Merge remote-tracking branch 'origin/main' into more-dollars

This commit is contained in:
Richard Feldman 2024-02-26 23:03:14 -05:00
commit 6f84e24fa5
No known key found for this signature in database
GPG key ID: F1F21AA5B1D9E43B
450 changed files with 14625 additions and 29764 deletions

View file

@ -1121,7 +1121,7 @@ fn lowlevel_spec<'a>(
// just dream up a unit value
builder.add_make_tuple(block, &[])
}
ListLen => {
ListLenUsize | ListLenU64 => {
// TODO should this touch the heap cell?
// just dream up a unit value
builder.add_make_tuple(block, &[])
@ -1173,6 +1173,16 @@ fn lowlevel_spec<'a>(
_ => unreachable!(),
}
}
ListClone => {
let list = env.symbols[&arguments[0]];
let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
let cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?;
let _unit = builder.add_update(block, update_mode_var, cell)?;
with_new_heap_cell(builder, block, bag)
}
ListSwap => {
let list = env.symbols[&arguments[0]];
@ -1220,7 +1230,7 @@ fn lowlevel_spec<'a>(
builder.add_make_tuple(block, &[cell, bag])
}
StrFromUtf8Range => {
StrFromUtf8 => {
let list = env.symbols[&arguments[0]];
let cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?;

View file

@ -12,6 +12,7 @@ roc_bitcode = { path = "../builtins/bitcode" }
roc_can = { path = "../can" }
roc_collections = { path = "../collections" }
roc_constrain = { path = "../constrain" }
roc_debug_flags = { path = "../debug_flags" }
roc_error_macros = { path = "../../error_macros" }
roc_gen_dev = { path = "../gen_dev", default-features = false }
roc_gen_llvm = { path = "../gen_llvm" }

View file

@ -1,6 +1,7 @@
use crate::target::{arch_str, target_zig_str};
use libloading::{Error, Library};
use roc_command_utils::{cargo, clang, rustup, zig};
use roc_debug_flags;
use roc_error_macros::internal_error;
use roc_mono::ir::OptLevel;
use std::collections::HashMap;
@ -537,7 +538,7 @@ pub fn rebuild_host(
// on windows, we need the nightly toolchain so we can use `-Z export-executable-symbols`
// using `+nightly` only works when running cargo through rustup
let mut cmd = rustup();
cmd.args(["run", "nightly-2023-05-28", "cargo"]);
cmd.args(["run", "nightly-2023-07-09", "cargo"]);
cmd
} else {
@ -613,7 +614,8 @@ pub fn rebuild_host(
// Clean up c_host.o
if c_host_dest.exists() {
std::fs::remove_file(c_host_dest).unwrap();
// there can be a race condition on this file cleanup
let _ = std::fs::remove_file(c_host_dest);
}
}
} else if rust_host_src.exists() {
@ -848,6 +850,17 @@ fn strs_to_path(strs: &[&str]) -> PathBuf {
strs.iter().collect()
}
fn extra_link_flags() -> Vec<String> {
match env::var("ROC_LINK_FLAGS") {
Ok(flags) => {
println!("⚠️ CAUTION: The ROC_LINK_FLAGS environment variable is a temporary workaround, and will no longer do anything once surgical linking lands! If you're concerned about what this means for your use case, please ask about it on Zulip.");
flags
}
Err(_) => "".to_string(),
}.split_whitespace().map(|x| x.to_owned()).collect()
}
fn link_linux(
target: &Triple,
output_path: PathBuf,
@ -1037,6 +1050,7 @@ fn link_linux(
.args(&base_args)
.args(["-dynamic-linker", ld_linux])
.args(input_paths)
.args(extra_link_flags())
// ld.lld requires this argument, and does not accept --arch
// .args(&["-L/usr/lib/x86_64-linux-gnu"])
.args([
@ -1054,6 +1068,7 @@ fn link_linux(
"-o",
output_path.as_path().to_str().unwrap(), // app (or app.so or app.dylib etc.)
]);
debug_print_command(&command);
let output = command.spawn()?;
@ -1108,7 +1123,8 @@ fn link_macos(
"-macos_version_min",
&get_macos_version(),
])
.args(input_paths);
.args(input_paths)
.args(extra_link_flags());
let sdk_path = "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib";
if Path::new(sdk_path).exists() {
@ -1116,18 +1132,6 @@ fn link_macos(
ld_command.arg(format!("-L{sdk_path}/swift"));
};
let roc_link_flags = match env::var("ROC_LINK_FLAGS") {
Ok(flags) => {
println!("⚠️ CAUTION: The ROC_LINK_FLAGS environment variable is a temporary workaround, and will no longer do anything once surgical linking lands! If you're concerned about what this means for your use case, please ask about it on Zulip.");
flags
}
Err(_) => "".to_string(),
};
for roc_link_flag in roc_link_flags.split_whitespace() {
ld_command.arg(roc_link_flag);
}
ld_command.args([
// Libraries - see https://github.com/roc-lang/roc/pull/554#discussion_r496392274
// for discussion and further references
@ -1162,14 +1166,18 @@ fn link_macos(
output_path.to_str().unwrap(), // app
]);
debug_print_command(&ld_command);
let mut ld_child = ld_command.spawn()?;
match target.architecture {
Architecture::Aarch64(_) => {
ld_child.wait()?;
let codesign_child = Command::new("codesign")
.args(["-s", "-", output_path.to_str().unwrap()])
.spawn()?;
let mut codesign_cmd = Command::new("codesign");
codesign_cmd.args(["-s", "-", output_path.to_str().unwrap()]);
debug_print_command(&codesign_cmd);
let codesign_child = codesign_cmd.spawn()?;
Ok((codesign_child, output_path))
}
@ -1178,8 +1186,11 @@ fn link_macos(
}
fn get_macos_version() -> String {
let cmd_stdout = Command::new("sw_vers")
.arg("-productVersion")
let mut cmd = Command::new("sw_vers");
cmd.arg("-productVersion");
debug_print_command(&cmd);
let cmd_stdout = cmd
.output()
.expect("Failed to execute command 'sw_vers -productVersion'")
.stdout;
@ -1221,6 +1232,7 @@ fn link_wasm32(
"-fstrip",
"-O",
"ReleaseSmall",
"-rdynamic",
// useful for debugging
// "-femit-llvm-ir=/home/folkertdev/roc/roc/crates/cli_testing_examples/benchmarks/platform/host.ll",
])
@ -1382,15 +1394,11 @@ pub fn preprocess_host_wasm32(host_input_path: &Path, preprocessed_host_path: &P
}
fn run_build_command(mut command: Command, file_to_build: &str, flaky_fail_counter: usize) {
let mut command_string = std::ffi::OsString::new();
command_string.push(command.get_program());
for arg in command.get_args() {
command_string.push(" ");
command_string.push(arg);
}
let cmd_str = command_string.to_str().unwrap();
let command_string = stringify_command(&command);
let cmd_str = &command_string;
roc_debug_flags::dbg_do!(roc_debug_flags::ROC_PRINT_BUILD_COMMANDS, {
print_command_str(cmd_str);
});
let cmd_output = command.output().unwrap();
let max_flaky_fail_count = 10;
@ -1428,3 +1436,41 @@ fn run_build_command(mut command: Command, file_to_build: &str, flaky_fail_count
}
}
}
/// Stringify a command for printing
/// e.g. `HOME=~ zig build-exe foo.zig -o foo`
fn stringify_command(cmd: &Command) -> String {
let mut command_string = std::ffi::OsString::new();
for (name, opt_val) in cmd.get_envs() {
command_string.push(name);
command_string.push("=");
if let Some(val) = opt_val {
command_string.push(val);
} else {
command_string.push("''");
}
command_string.push(" ");
}
command_string.push(cmd.get_program());
for arg in cmd.get_args() {
command_string.push(" ");
command_string.push(arg);
}
String::from(command_string.to_str().unwrap())
}
#[cfg(debug_assertions)]
fn print_command_str(s: &str) {
println!("\nRoc build command:\n{}\n", s);
}
fn debug_print_command(_cmd: &Command) {
// This debug macro is compiled out in release mode, so the argument is unused
roc_debug_flags::dbg_do!(roc_debug_flags::ROC_PRINT_BUILD_COMMANDS, {
print_command_str(&stringify_command(_cmd));
});
}

View file

@ -86,6 +86,7 @@ pub struct CodeGenOptions {
pub opt_level: OptLevel,
pub emit_debug_info: bool,
pub emit_llvm_ir: bool,
pub fuzz: bool,
}
type GenFromMono<'a> = (CodeObject, CodeGenTiming, ExpectMetadata<'a>);
@ -103,6 +104,7 @@ pub fn gen_from_mono_module<'a>(
let path = roc_file_path;
let debug = code_gen_options.emit_debug_info;
let emit_llvm_ir = code_gen_options.emit_llvm_ir;
let fuzz = code_gen_options.fuzz;
let opt = code_gen_options.opt_level;
match code_gen_options.backend {
@ -131,6 +133,7 @@ pub fn gen_from_mono_module<'a>(
backend_mode,
debug,
emit_llvm_ir,
fuzz,
),
}
}
@ -148,6 +151,7 @@ fn gen_from_mono_module_llvm<'a>(
backend_mode: LlvmBackendMode,
emit_debug_info: bool,
emit_llvm_ir: bool,
fuzz: bool,
) -> GenFromMono<'a> {
use crate::target::{self, convert_opt_level};
use inkwell::attributes::{Attribute, AttributeLoc};
@ -284,7 +288,8 @@ fn gen_from_mono_module_llvm<'a>(
// annotate the LLVM IR output with debug info
// so errors are reported with the line number of the LLVM source
let memory_buffer = if cfg!(feature = "sanitizers") && std::env::var("ROC_SANITIZERS").is_ok() {
let gen_sanitizers = cfg!(feature = "sanitizers") && std::env::var("ROC_SANITIZERS").is_ok();
let memory_buffer = if fuzz || gen_sanitizers {
let dir = tempfile::tempdir().unwrap();
let dir = dir.into_path();
@ -301,33 +306,27 @@ fn gen_from_mono_module_llvm<'a>(
let mut passes = vec![];
let mut extra_args = vec![];
let mut unrecognized = vec![];
for sanitizer in std::env::var("ROC_SANITIZERS")
.unwrap()
.split(',')
.map(|x| x.trim())
{
match sanitizer {
"address" => passes.push("asan-module"),
"memory" => passes.push("msan-module"),
"thread" => passes.push("tsan-module"),
"cargo-fuzz" => {
passes.push("sancov-module");
extra_args.extend_from_slice(&[
"-sanitizer-coverage-level=3",
"-sanitizer-coverage-prune-blocks=0",
"-sanitizer-coverage-inline-8bit-counters",
"-sanitizer-coverage-pc-table",
]);
if fuzz {
passes.push("sancov-module");
extra_args.extend_from_slice(&[
"-sanitizer-coverage-level=4",
"-sanitizer-coverage-inline-8bit-counters",
"-sanitizer-coverage-pc-table",
"-sanitizer-coverage-trace-compares",
]);
}
if gen_sanitizers {
for sanitizer in std::env::var("ROC_SANITIZERS")
.unwrap()
.split(',')
.map(|x| x.trim())
{
match sanitizer {
"address" => passes.push("asan-module"),
"memory" => passes.push("msan-module"),
"thread" => passes.push("tsan-module"),
x => unrecognized.push(x.to_owned()),
}
"afl.rs" => {
passes.push("sancov-module");
extra_args.extend_from_slice(&[
"-sanitizer-coverage-level=3",
"-sanitizer-coverage-prune-blocks=0",
"-sanitizer-coverage-trace-pc-guard",
]);
}
x => unrecognized.push(x.to_owned()),
}
}
if !unrecognized.is_empty() {
@ -802,7 +801,7 @@ fn build_loaded_file<'a>(
platform_main_roc.with_file_name(roc_linker::preprocessed_host_filename(target).unwrap())
};
let mut output_exe_path = match out_path {
let output_exe_path = match out_path {
Some(path) => {
// true iff the path ends with a directory separator,
// e.g. '/' on UNIX, '/' or '\\' on Windows
@ -830,12 +829,22 @@ fn build_loaded_file<'a>(
if ends_with_sep {
let filename = app_module_path.file_name().unwrap_or_default();
with_executable_extension(&path.join(filename), operating_system)
with_output_extension(
&path.join(filename),
operating_system,
linking_strategy,
link_type,
)
} else {
path.to_path_buf()
}
}
None => with_executable_extension(&app_module_path, operating_system),
None => with_output_extension(
&app_module_path,
operating_system,
linking_strategy,
link_type,
),
};
// We don't need to spawn a rebuild thread when using a prebuilt host.
@ -994,7 +1003,6 @@ fn build_loaded_file<'a>(
}
(LinkingStrategy::Additive, _) | (LinkingStrategy::Legacy, LinkType::None) => {
// Just copy the object file to the output folder.
output_exe_path.set_extension(operating_system.object_file_ext());
std::fs::write(&output_exe_path, &*roc_app_bytes).unwrap();
}
(LinkingStrategy::Legacy, _) => {
@ -1282,6 +1290,7 @@ pub fn build_str_test<'a>(
opt_level: OptLevel::Normal,
emit_debug_info: false,
emit_llvm_ir: false,
fuzz: false,
};
let emit_timings = false;
@ -1324,6 +1333,17 @@ pub fn build_str_test<'a>(
)
}
fn with_executable_extension(path: &Path, os: OperatingSystem) -> PathBuf {
path.with_extension(os.executable_file_ext().unwrap_or_default())
fn with_output_extension(
path: &Path,
os: OperatingSystem,
linking_strategy: LinkingStrategy,
link_type: LinkType,
) -> PathBuf {
match (linking_strategy, link_type) {
(LinkingStrategy::Additive, _) | (LinkingStrategy::Legacy, LinkType::None) => {
// Additive linking and no linking both output the object file type.
path.with_extension(os.object_file_ext())
}
_ => path.with_extension(os.executable_file_ext().unwrap_or_default()),
}
}

View file

@ -1 +0,0 @@
builtins.ll

View file

@ -6,7 +6,7 @@ Builtins are the functions and modules that are implicitly imported into every m
Edit the appropriate `roc/*.roc` file with your new implementation. All normal rules for writing Roc code apply. Be sure to add a declaration, definition, some documentation and add it to the exposes list it in the module head.
Next, look towards the bottom of the `compiler/module/src/symbol.rs` file. Inside the `define_builtins!` macro, there is a list for each of the builtin modules and the function or value names it contains. Add a new entry to the appropriate list for your new function.
Next, look towards the bottom of the `compiler/module/src/symbol.rs` file. Inside the `define_builtins!` macro, there is a list for each of the builtin modules and the function or value names it contains. Add a new entry to the appropriate list for your new function.
For each of the builtin modules, there is a file in `compiler/test_gen/src/` like `gen_num.rs`, `gen_str.rs` etc. Add new tests for the module you are changing to the appropriate file here. You can look at the existing test cases for examples and inspiration.
@ -22,14 +22,14 @@ Some of these have `#` inside their name (`first#list`, `#lt` ..). This is a tri
But we can use these values and some of these are necessary for implementing builtins. For example, `List.get` returns tags, and it is not easy for us to create tags when composing LLVM. What is easier however, is:
- ..writing `List.#getUnsafe` that has the dangerous signature of `List elem, Nat -> elem` in LLVM
- ..writing `List elem, Nat -> Result elem [OutOfBounds]*` in a type safe way that uses `getUnsafe` internally, only after it checks if the `elem` at `Nat` index exists.
- ..writing `List.#getUnsafe` that has the dangerous signature of `List elem, U64 -> elem` in LLVM
- ..writing `List elem, U64 -> Result elem [OutOfBounds]*` in a type safe way that uses `getUnsafe` internally, only after it checks if the `elem` at `U64` index exists.
### can/src/builtins.rs
Right at the top of this module is a function called `builtin_defs`. All this is doing is mapping the `Symbol` defined in `module/src/symbol.rs` to its implementation. Some of the builtins are quite complex, such as `list_get`. What makes `list_get` is that it returns tags, and in order to return tags it first has to defer to lower-level functions via an if statement.
Right at the top of this module is a function called `builtin_defs`. All this is doing is mapping the `Symbol` defined in `module/src/symbol.rs` to its implementation. Some of the builtins are quite complex, such as `list_get`. What makes `list_get` is that it returns tags, and in order to return tags it first has to defer to lower-level functions via an if statement.
Lets look at `List.repeat : elem, Nat -> List elem`, which is more straight-forward, and points directly to its lower level implementation:
Lets look at `List.repeat : elem, U64 -> List elem`, which is more straightforward, and points directly to its lower level implementation:
```rust
fn list_repeat(symbol: Symbol, var_store: &mut VarStore) -> Def {
@ -106,7 +106,7 @@ fn atan() {
But replace `Num.atan` and the type signature with the new builtin.
### test_gen/test/*.rs
### test_gen/test/\*.rs
In this directory, there are a couple files like `gen_num.rs`, `gen_str.rs`, etc. For the `Str` module builtins, put the test in `gen_str.rs`, etc. Find the one for the new builtin, and add a test like:
@ -123,5 +123,5 @@ But replace `Num.atan`, the return value, and the return type with your new buil
When implementing a new builtin, it is often easy to copy and paste the implementation for an existing builtin. This can take you quite far since many builtins are very similar, but it also risks forgetting to change one small part of what you copy and pasted and losing a lot of time later on when you cant figure out why things don't work. So, speaking from experience, even if you are copying an existing builtin, try and implement it manually without copying and pasting. Two recent instances of this (as of September 7th, 2020):
- `List.keepIf` did not work for a long time because in builtins its `LowLevel` was `ListMap`. This was because I copy and pasted the `List.map` implementation in `builtins.rs
- `List.walkBackwards` had mysterious memory bugs for a little while because in `unique.rs` its return type was `list_type(flex(b))` instead of `flex(b)` since it was copy and pasted from `List.keepIf`.
- `List.keepIf` did not work for a long time because in builtins its `LowLevel` was `ListMap`. This was because I copy and pasted the `List.map` implementation in `builtins.rs
- `List.walkBackwards` had mysterious memory bugs for a little while because in `unique.rs` its return type was `list_type(flex(b))` instead of `flex(b)` since it was copy and pasted from `List.keepIf`.

View file

@ -1,5 +0,0 @@
zig-out
zig-cache
src/zig-cache
benchmark/zig-cache
dec

View file

@ -24,6 +24,9 @@ pub const RocDec = extern struct {
pub const one_point_zero_i128: i128 = math.pow(i128, 10, RocDec.decimal_places);
pub const one_point_zero: RocDec = .{ .num = one_point_zero_i128 };
pub const two_point_zero: RocDec = RocDec.add(RocDec.one_point_zero, RocDec.one_point_zero);
pub const zero_point_five: RocDec = RocDec.div(RocDec.one_point_zero, RocDec.two_point_zero);
pub fn fromU64(num: u64) RocDec {
return .{ .num = num * one_point_zero_i128 };
}
@ -340,6 +343,77 @@ pub const RocDec = extern struct {
}
}
fn trunc(self: RocDec) RocDec {
return RocDec.sub(self, self.fract());
}
fn fract(self: RocDec) RocDec {
const sign = std.math.sign(self.num);
const digits = @mod(sign * self.num, RocDec.one_point_zero.num);
return RocDec{ .num = sign * digits };
}
// Returns the nearest integer to self. If a value is half-way between two integers, round away from 0.0.
fn round(arg1: RocDec) RocDec {
// this rounds towards zero
const tmp = arg1.trunc();
const sign = std.math.sign(arg1.num);
const abs_fract = sign * arg1.fract().num;
if (abs_fract >= RocDec.zero_point_five.num) {
return RocDec.add(tmp, RocDec{ .num = sign * RocDec.one_point_zero.num });
} else {
return tmp;
}
}
// Returns the largest integer less than or equal to itself
fn floor(arg1: RocDec) RocDec {
const tmp = arg1.trunc();
if (arg1.num < 0 and arg1.fract().num != 0) {
return RocDec.sub(tmp, RocDec.one_point_zero);
} else {
return tmp;
}
}
// Returns the smallest integer greater than or equal to itself
fn ceiling(arg1: RocDec) RocDec {
const tmp = arg1.trunc();
if (arg1.num > 0 and arg1.fract().num != 0) {
return RocDec.add(tmp, RocDec.one_point_zero);
} else {
return tmp;
}
}
fn powInt(base: RocDec, exponent: i128) RocDec {
if (exponent == 0) {
return RocDec.one_point_zero;
} else if (exponent > 0) {
if (@mod(exponent, 2) == 0) {
const half_power = RocDec.powInt(base, exponent >> 1); // `>> 1` == `/ 2`
return RocDec.mul(half_power, half_power);
} else {
return RocDec.mul(base, RocDec.powInt(base, exponent - 1));
}
} else {
return RocDec.div(RocDec.one_point_zero, RocDec.powInt(base, -exponent));
}
}
fn pow(base: RocDec, exponent: RocDec) RocDec {
if (exponent.trunc().num == exponent.num) {
return base.powInt(@divTrunc(exponent.num, RocDec.one_point_zero_i128));
} else {
return fromF64(std.math.pow(f64, base.toF64(), exponent.toF64())).?;
}
}
pub fn mul(self: RocDec, other: RocDec) RocDec {
const answer = RocDec.mulWithOverflow(self, other);
@ -1195,6 +1269,153 @@ test "log: 1" {
try expectEqual(RocDec.fromU64(0), RocDec.log(RocDec.fromU64(1)));
}
test "fract: 0" {
var roc_str = RocStr.init("0", 1);
var dec = RocDec.fromStr(roc_str).?;
try expectEqual(RocDec{ .num = 0 }, dec.fract());
}
test "fract: 1" {
var roc_str = RocStr.init("1", 1);
var dec = RocDec.fromStr(roc_str).?;
try expectEqual(RocDec{ .num = 0 }, dec.fract());
}
test "fract: 123.45" {
var roc_str = RocStr.init("123.45", 6);
var dec = RocDec.fromStr(roc_str).?;
try expectEqual(RocDec{ .num = 450000000000000000 }, dec.fract());
}
test "fract: -123.45" {
var roc_str = RocStr.init("-123.45", 7);
var dec = RocDec.fromStr(roc_str).?;
try expectEqual(RocDec{ .num = -450000000000000000 }, dec.fract());
}
test "fract: .45" {
var roc_str = RocStr.init(".45", 3);
var dec = RocDec.fromStr(roc_str).?;
try expectEqual(RocDec{ .num = 450000000000000000 }, dec.fract());
}
test "fract: -0.00045" {
const dec: RocDec = .{ .num = -450000000000000 };
const res = dec.fract();
try expectEqual(dec.num, res.num);
}
test "trunc: 0" {
var roc_str = RocStr.init("0", 1);
var dec = RocDec.fromStr(roc_str).?;
try expectEqual(RocDec{ .num = 0 }, dec.trunc());
}
test "trunc: 1" {
var roc_str = RocStr.init("1", 1);
var dec = RocDec.fromStr(roc_str).?;
try expectEqual(RocDec.one_point_zero, dec.trunc());
}
test "trunc: 123.45" {
var roc_str = RocStr.init("123.45", 6);
var dec = RocDec.fromStr(roc_str).?;
try expectEqual(RocDec{ .num = 123000000000000000000 }, dec.trunc());
}
test "trunc: -123.45" {
var roc_str = RocStr.init("-123.45", 7);
var dec = RocDec.fromStr(roc_str).?;
try expectEqual(RocDec{ .num = -123000000000000000000 }, dec.trunc());
}
test "trunc: .45" {
var roc_str = RocStr.init(".45", 3);
var dec = RocDec.fromStr(roc_str).?;
try expectEqual(RocDec{ .num = 0 }, dec.trunc());
}
test "trunc: -0.00045" {
const dec: RocDec = .{ .num = -450000000000000 };
const res = dec.trunc();
try expectEqual(RocDec{ .num = 0 }, res);
}
test "round: 123.45" {
var roc_str = RocStr.init("123.45", 6);
var dec = RocDec.fromStr(roc_str).?;
try expectEqual(RocDec{ .num = 123000000000000000000 }, dec.round());
}
test "round: -123.45" {
var roc_str = RocStr.init("-123.45", 7);
var dec = RocDec.fromStr(roc_str).?;
try expectEqual(RocDec{ .num = -123000000000000000000 }, dec.round());
}
test "round: 0.5" {
var roc_str = RocStr.init("0.5", 3);
var dec = RocDec.fromStr(roc_str).?;
try expectEqual(RocDec.one_point_zero, dec.round());
}
test "round: -0.5" {
var roc_str = RocStr.init("-0.5", 4);
var dec = RocDec.fromStr(roc_str).?;
try expectEqual(RocDec{ .num = -1000000000000000000 }, dec.round());
}
test "powInt: 3.1 ^ 0" {
var roc_str = RocStr.init("3.1", 3);
var dec = RocDec.fromStr(roc_str).?;
try expectEqual(RocDec.one_point_zero, dec.powInt(0));
}
test "powInt: 3.1 ^ 1" {
var roc_str = RocStr.init("3.1", 3);
var dec = RocDec.fromStr(roc_str).?;
try expectEqual(dec, dec.powInt(1));
}
test "powInt: 2 ^ 2" {
var roc_str = RocStr.init("4", 1);
var dec = RocDec.fromStr(roc_str).?;
try expectEqual(dec, RocDec.two_point_zero.powInt(2));
}
test "powInt: 0.5 ^ 2" {
var roc_str = RocStr.init("0.25", 4);
var dec = RocDec.fromStr(roc_str).?;
try expectEqual(dec, RocDec.zero_point_five.powInt(2));
}
test "pow: 0.5 ^ 2.0" {
var roc_str = RocStr.init("0.25", 4);
var dec = RocDec.fromStr(roc_str).?;
try expectEqual(dec, RocDec.zero_point_five.pow(RocDec.two_point_zero));
}
// exports
pub fn fromStr(arg: RocStr) callconv(.C) num_.NumParseResult(i128) {
@ -1295,6 +1516,10 @@ pub fn logC(arg: RocDec) callconv(.C) i128 {
return @call(.always_inline, RocDec.log, .{arg}).num;
}
pub fn powC(arg1: RocDec, arg2: RocDec) callconv(.C) i128 {
return @call(.always_inline, RocDec.pow, .{ arg1, arg2 }).num;
}
pub fn sinC(arg: RocDec) callconv(.C) i128 {
return @call(.always_inline, RocDec.sin, .{arg}).num;
}
@ -1342,3 +1567,30 @@ pub fn mulOrPanicC(arg1: RocDec, arg2: RocDec) callconv(.C) RocDec {
pub fn mulSaturatedC(arg1: RocDec, arg2: RocDec) callconv(.C) RocDec {
return @call(.always_inline, RocDec.mulSaturated, .{ arg1, arg2 });
}
pub fn exportRound(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(input: RocDec) callconv(.C) T {
return @as(T, @intCast(@divFloor(input.round().num, RocDec.one_point_zero_i128)));
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}
pub fn exportFloor(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(input: RocDec) callconv(.C) T {
return @as(T, @intCast(@divFloor(input.floor().num, RocDec.one_point_zero_i128)));
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}
pub fn exportCeiling(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(input: RocDec) callconv(.C) T {
return @as(T, @intCast(@divFloor(input.ceiling().num, RocDec.one_point_zero_i128)));
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}

File diff suppressed because it is too large Load diff

View file

@ -472,7 +472,7 @@ pub fn listMap4(
}
pub fn listWithCapacity(
capacity: usize,
capacity: u64,
alignment: u32,
element_width: usize,
) callconv(.C) RocList {
@ -482,16 +482,22 @@ pub fn listWithCapacity(
pub fn listReserve(
list: RocList,
alignment: u32,
spare: usize,
spare: u64,
element_width: usize,
update_mode: UpdateMode,
) callconv(.C) RocList {
const old_length = list.len();
if ((update_mode == .InPlace or list.isUnique()) and list.getCapacity() >= list.len() + spare) {
const original_len = list.len();
const cap = @as(u64, @intCast(list.getCapacity()));
const desired_cap = @as(u64, @intCast(original_len)) +| spare;
if ((update_mode == .InPlace or list.isUnique()) and cap >= desired_cap) {
return list;
} else {
var output = list.reallocate(alignment, old_length + spare, element_width);
output.length = old_length;
// Make sure on 32-bit targets we don't accidentally wrap when we cast our U64 desired capacity to U32.
const reserve_size: u64 = @min(desired_cap, @as(u64, @intCast(std.math.maxInt(usize))));
var output = list.reallocate(alignment, @as(usize, @intCast(reserve_size)), element_width);
output.length = original_len;
return output;
}
}
@ -577,13 +583,13 @@ pub fn listSwap(
list: RocList,
alignment: u32,
element_width: usize,
index_1: usize,
index_2: usize,
index_1: u64,
index_2: u64,
update_mode: UpdateMode,
) callconv(.C) RocList {
const size = list.len();
const size = @as(u64, @intCast(list.len()));
if (index_1 == index_2 or index_1 >= size or index_2 >= size) {
// Either index out of bounds so we just return
// Either one index was out of bounds, or both indices were the same; just return
return list;
}
@ -596,7 +602,11 @@ pub fn listSwap(
};
const source_ptr = @as([*]u8, @ptrCast(newList.bytes));
swapElements(source_ptr, element_width, index_1, index_2);
swapElements(source_ptr, element_width, @as(usize,
// We already verified that both indices are less than the stored list length,
// which is usize, so casting them to usize will definitely be lossless.
@intCast(index_1)), @as(usize, @intCast(index_2)));
return newList;
}
@ -605,12 +615,12 @@ pub fn listSublist(
list: RocList,
alignment: u32,
element_width: usize,
start: usize,
len: usize,
start_u64: u64,
len_u64: u64,
dec: Dec,
) callconv(.C) RocList {
const size = list.len();
if (len == 0 or start >= size) {
if (size == 0 or start_u64 >= @as(u64, @intCast(size))) {
// Decrement the reference counts of all elements.
if (list.bytes) |source_ptr| {
var i: usize = 0;
@ -629,9 +639,26 @@ pub fn listSublist(
}
if (list.bytes) |source_ptr| {
const keep_len = @min(len, size - start);
// This cast is lossless because we would have early-returned already
// if `start_u64` were greater than `size`, and `size` fits in usize.
const start: usize = @intCast(start_u64);
const drop_start_len = start;
const drop_end_len = size - (start + keep_len);
// (size - start) can't overflow because we would have early-returned already
// if `start` were greater than `size`.
const size_minus_start = size - start;
// This outer cast to usize is lossless. size, start, and size_minus_start all fit in usize,
// and @min guarantees that if `len_u64` gets returned, it's because it was smaller
// than something that fit in usize.
const keep_len = @as(usize, @intCast(@min(len_u64, @as(u64, @intCast(size_minus_start)))));
// This can't overflow because if len > size_minus_start,
// then keep_len == size_minus_start and this will be 0.
// Alternatively, if len <= size_minus_start, then keep_len will
// be equal to len, meaning keep_len <= size_minus_start too,
// which in turn means this won't overflow.
const drop_end_len = size_minus_start - keep_len;
// Decrement the reference counts of elements before `start`.
var i: usize = 0;
@ -671,28 +698,33 @@ pub fn listDropAt(
list: RocList,
alignment: u32,
element_width: usize,
drop_index: usize,
drop_index_u64: u64,
dec: Dec,
) callconv(.C) RocList {
const size = list.len();
const size_u64 = @as(u64, @intCast(size));
// If droping the first or last element, return a seamless slice.
// For simplicity, do this by calling listSublist.
// In the future, we can test if it is faster to manually inline the important parts here.
if (drop_index == 0) {
if (drop_index_u64 == 0) {
return listSublist(list, alignment, element_width, 1, size -| 1, dec);
} else if (drop_index == size -| 1) {
} else if (drop_index_u64 == size_u64 - 1) { // It's fine if (size - 1) wraps on size == 0 here,
// because if size is 0 then it's always fine for this branch to be taken; no
// matter what drop_index was, we're size == 0, so empty list will always be returned.
return listSublist(list, alignment, element_width, 0, size -| 1, dec);
}
if (list.bytes) |source_ptr| {
if (drop_index >= size) {
if (drop_index_u64 >= size_u64) {
return list;
}
if (drop_index < size) {
const element = source_ptr + drop_index * element_width;
dec(element);
}
// This cast must be lossless, because we would have just early-returned if drop_index
// were >= than `size`, and we know `size` fits in usize.
const drop_index: usize = @intCast(drop_index_u64);
const element = source_ptr + drop_index * element_width;
dec(element);
// NOTE
// we need to return an empty list explicitly,
@ -906,7 +938,7 @@ pub fn listConcat(list_a: RocList, list_b: RocList, alignment: u32, element_widt
pub fn listReplaceInPlace(
list: RocList,
index: usize,
index: u64,
element: Opaque,
element_width: usize,
out_element: ?[*]u8,
@ -916,14 +948,15 @@ pub fn listReplaceInPlace(
// at the time of writing, the function is implemented roughly as
// `if inBounds then LowLevelListReplace input index item else input`
// so we don't do a bounds check here. Hence, the list is also non-empty,
// because inserting into an empty list is always out of bounds
return listReplaceInPlaceHelp(list, index, element, element_width, out_element);
// because inserting into an empty list is always out of bounds,
// and it's always safe to cast index to usize.
return listReplaceInPlaceHelp(list, @as(usize, @intCast(index)), element, element_width, out_element);
}
pub fn listReplace(
list: RocList,
alignment: u32,
index: usize,
index: u64,
element: Opaque,
element_width: usize,
out_element: ?[*]u8,
@ -933,8 +966,9 @@ pub fn listReplace(
// at the time of writing, the function is implemented roughly as
// `if inBounds then LowLevelListReplace input index item else input`
// so we don't do a bounds check here. Hence, the list is also non-empty,
// because inserting into an empty list is always out of bounds
return listReplaceInPlaceHelp(list.makeUnique(alignment, element_width), index, element, element_width, out_element);
// because inserting into an empty list is always out of bounds,
// and it's always safe to cast index to usize.
return listReplaceInPlaceHelp(list.makeUnique(alignment, element_width), @as(usize, @intCast(index)), element, element_width, out_element);
}
inline fn listReplaceInPlaceHelp(
@ -962,6 +996,14 @@ pub fn listIsUnique(
return list.isEmpty() or list.isUnique();
}
pub fn listClone(
list: RocList,
alignment: u32,
element_width: usize,
) callconv(.C) RocList {
return list.makeUnique(alignment, element_width);
}
pub fn listCapacity(
list: RocList,
) callconv(.C) usize {

View file

@ -36,6 +36,7 @@ comptime {
exportDecFn(dec.fromStr, "from_str");
exportDecFn(dec.fromU64C, "from_u64");
exportDecFn(dec.logC, "log");
exportDecFn(dec.powC, "pow");
exportDecFn(dec.mulC, "mul_with_overflow");
exportDecFn(dec.mulOrPanicC, "mul_or_panic");
exportDecFn(dec.mulSaturatedC, "mul_saturated");
@ -52,6 +53,10 @@ comptime {
inline for (INTEGERS) |T| {
dec.exportFromInt(T, ROC_BUILTINS ++ ".dec.from_int.");
dec.exportRound(T, ROC_BUILTINS ++ ".dec.round.");
dec.exportFloor(T, ROC_BUILTINS ++ ".dec.floor.");
dec.exportCeiling(T, ROC_BUILTINS ++ ".dec.ceiling.");
}
}
@ -75,6 +80,7 @@ comptime {
exportListFn(list.listReplaceInPlace, "replace_in_place");
exportListFn(list.listSwap, "swap");
exportListFn(list.listIsUnique, "is_unique");
exportListFn(list.listClone, "clone");
exportListFn(list.listCapacity, "capacity");
exportListFn(list.listAllocationPtr, "allocation_ptr");
exportListFn(list.listReleaseExcessCapacity, "release_excess_capacity");
@ -89,11 +95,6 @@ const FLOATS = [_]type{ f32, f64 };
const NUMBERS = INTEGERS ++ FLOATS;
comptime {
exportNumFn(num.bytesToU16C, "bytes_to_u16");
exportNumFn(num.bytesToU32C, "bytes_to_u32");
exportNumFn(num.bytesToU64C, "bytes_to_u64");
exportNumFn(num.bytesToU128C, "bytes_to_u128");
exportNumFn(num.shiftRightZeroFillI128, "shift_right_zero_fill.i128");
exportNumFn(num.shiftRightZeroFillU128, "shift_right_zero_fill.u128");
@ -110,19 +111,6 @@ comptime {
exportNumFn(num.greaterThanU128, "greater_than.u128");
exportNumFn(num.greaterThanOrEqualU128, "greater_than_or_equal.u128");
exportNumFn(num.compareI128, "compare.i128");
exportNumFn(num.compareU128, "compare.u128");
exportNumFn(num.lessThanI128, "less_than.i128");
exportNumFn(num.lessThanOrEqualI128, "less_than_or_equal.i128");
exportNumFn(num.greaterThanI128, "greater_than.i128");
exportNumFn(num.greaterThanOrEqualI128, "greater_than_or_equal.i128");
exportNumFn(num.lessThanU128, "less_than.u128");
exportNumFn(num.lessThanOrEqualU128, "less_than_or_equal.u128");
exportNumFn(num.greaterThanU128, "greater_than.u128");
exportNumFn(num.greaterThanOrEqualU128, "greater_than_or_equal.u128");
inline for (INTEGERS, 0..) |T, i| {
num.exportPow(T, ROC_BUILTINS ++ "." ++ NUM ++ ".pow_int.");
num.exportDivCeil(T, ROC_BUILTINS ++ "." ++ NUM ++ ".div_ceil.");
@ -134,6 +122,9 @@ comptime {
num.exportCeiling(f32, T, ROC_BUILTINS ++ "." ++ NUM ++ ".ceiling_f32.");
num.exportCeiling(f64, T, ROC_BUILTINS ++ "." ++ NUM ++ ".ceiling_f64.");
num.exportNumToFloatCast(T, f32, ROC_BUILTINS ++ "." ++ NUM ++ ".num_to_float_cast_f32.");
num.exportNumToFloatCast(T, f64, ROC_BUILTINS ++ "." ++ NUM ++ ".num_to_float_cast_f64.");
num.exportAddWithOverflow(T, ROC_BUILTINS ++ "." ++ NUM ++ ".add_with_overflow.");
num.exportAddOrPanic(T, ROC_BUILTINS ++ "." ++ NUM ++ ".add_or_panic.");
num.exportAddSaturatedInt(T, ROC_BUILTINS ++ "." ++ NUM ++ ".add_saturated.");
@ -190,34 +181,28 @@ comptime {
const str = @import("str.zig");
comptime {
exportStrFn(str.init, "init");
exportStrFn(str.strToScalarsC, "to_scalars");
exportStrFn(str.strSplit, "str_split");
exportStrFn(str.countSegments, "count_segments");
exportStrFn(str.countGraphemeClusters, "count_grapheme_clusters");
exportStrFn(str.countUtf8Bytes, "count_utf8_bytes");
exportStrFn(str.isEmpty, "is_empty");
exportStrFn(str.getCapacity, "capacity");
exportStrFn(str.startsWith, "starts_with");
exportStrFn(str.startsWithScalar, "starts_with_scalar");
exportStrFn(str.endsWith, "ends_with");
exportStrFn(str.strConcatC, "concat");
exportStrFn(str.strJoinWithC, "joinWith");
exportStrFn(str.strNumberOfBytes, "number_of_bytes");
exportStrFn(str.strEqual, "equal");
exportStrFn(str.substringUnsafe, "substring_unsafe");
exportStrFn(str.getUnsafe, "get_unsafe");
exportStrFn(str.reserve, "reserve");
exportStrFn(str.getScalarUnsafe, "get_scalar_unsafe");
exportStrFn(str.appendScalar, "append_scalar");
exportStrFn(str.substringUnsafeC, "substring_unsafe");
exportStrFn(str.getUnsafeC, "get_unsafe");
exportStrFn(str.reserveC, "reserve");
exportStrFn(str.strToUtf8C, "to_utf8");
exportStrFn(str.fromUtf8RangeC, "from_utf8_range");
exportStrFn(str.repeat, "repeat");
exportStrFn(str.fromUtf8C, "from_utf8");
exportStrFn(str.repeatC, "repeat");
exportStrFn(str.strTrim, "trim");
exportStrFn(str.strTrimStart, "trim_start");
exportStrFn(str.strTrimEnd, "trim_end");
exportStrFn(str.strCloneTo, "clone_to");
exportStrFn(str.withCapacity, "with_capacity");
exportStrFn(str.strGraphemes, "graphemes");
exportStrFn(str.withCapacityC, "with_capacity");
exportStrFn(str.strAllocationPtr, "allocation_ptr");
exportStrFn(str.strReleaseExcessCapacity, "release_excess_capacity");
@ -264,6 +249,9 @@ comptime {
if (builtin.target.cpu.arch == .aarch64) {
@export(__roc_force_setjmp, .{ .name = "__roc_force_setjmp", .linkage = .Weak });
@export(__roc_force_longjmp, .{ .name = "__roc_force_longjmp", .linkage = .Weak });
} else if (builtin.os.tag == .windows) {
@export(__roc_force_setjmp_windows, .{ .name = "__roc_force_setjmp", .linkage = .Weak });
@export(__roc_force_longjmp_windows, .{ .name = "__roc_force_longjmp", .linkage = .Weak });
}
}
@ -279,14 +267,103 @@ pub extern fn _longjmp([*c]c_int, c_int) noreturn;
pub extern fn sigsetjmp([*c]c_int, c_int) c_int;
pub extern fn siglongjmp([*c]c_int, c_int) noreturn;
pub extern fn longjmperror() void;
// Zig won't expose the externs (and hence link correctly) unless we force them to be used.
fn __roc_force_setjmp(it: [*c]c_int) callconv(.C) c_int {
return setjmp(it);
}
fn __roc_force_longjmp(a0: [*c]c_int, a1: c_int) callconv(.C) noreturn {
longjmp(a0, a1);
}
pub extern fn windows_setjmp([*c]c_int) c_int;
pub extern fn windows_longjmp([*c]c_int, c_int) noreturn;
fn __roc_force_setjmp_windows(it: [*c]c_int) callconv(.C) c_int {
return windows_setjmp(it);
}
fn __roc_force_longjmp_windows(a0: [*c]c_int, a1: c_int) callconv(.C) noreturn {
windows_longjmp(a0, a1);
}
comptime {
if (builtin.os.tag == .windows) {
asm (
\\.global windows_longjmp;
\\windows_longjmp:
\\ movq 0x00(%rcx), %rdx
\\ movq 0x08(%rcx), %rbx
\\ # note 0x10 is not used yet!
\\ movq 0x18(%rcx), %rbp
\\ movq 0x20(%rcx), %rsi
\\ movq 0x28(%rcx), %rdi
\\ movq 0x30(%rcx), %r12
\\ movq 0x38(%rcx), %r13
\\ movq 0x40(%rcx), %r14
\\ movq 0x48(%rcx), %r15
\\
\\ # restore stack pointer
\\ movq 0x10(%rcx), %rsp
\\
\\ # load jmp address
\\ movq 0x50(%rcx), %r8
\\
\\ # set up return value
\\ movq %rbx, %rax
\\
\\ movdqu 0x60(%rcx), %xmm6
\\ movdqu 0x70(%rcx), %xmm7
\\ movdqu 0x80(%rcx), %xmm8
\\ movdqu 0x90(%rcx), %xmm9
\\ movdqu 0xa0(%rcx), %xmm10
\\ movdqu 0xb0(%rcx), %xmm11
\\ movdqu 0xc0(%rcx), %xmm12
\\ movdqu 0xd0(%rcx), %xmm13
\\ movdqu 0xe0(%rcx), %xmm14
\\ movdqu 0xf0(%rcx), %xmm15
\\
\\ jmp *%r8
\\
\\.global windows_setjmp;
\\windows_setjmp:
\\ movq %rdx, 0x00(%rcx)
\\ movq %rbx, 0x08(%rcx)
\\ # note 0x10 is not used yet!
\\ movq %rbp, 0x18(%rcx)
\\ movq %rsi, 0x20(%rcx)
\\ movq %rdi, 0x28(%rcx)
\\ movq %r12, 0x30(%rcx)
\\ movq %r13, 0x38(%rcx)
\\ movq %r14, 0x40(%rcx)
\\ movq %r15, 0x48(%rcx)
\\
\\ # the stack location right after the windows_setjmp call
\\ leaq 0x08(%rsp), %r8
\\ movq %r8, 0x10(%rcx)
\\
\\ movq (%rsp), %r8
\\ movq %r8, 0x50(%rcx)
\\
\\ movdqu %xmm6, 0x60(%rcx)
\\ movdqu %xmm7, 0x70(%rcx)
\\ movdqu %xmm8, 0x80(%rcx)
\\ movdqu %xmm9, 0x90(%rcx)
\\ movdqu %xmm10, 0xa0(%rcx)
\\ movdqu %xmm11, 0xb0(%rcx)
\\ movdqu %xmm12, 0xc0(%rcx)
\\ movdqu %xmm13, 0xd0(%rcx)
\\ movdqu %xmm14, 0xe0(%rcx)
\\ movdqu %xmm15, 0xf0(%rcx)
\\
\\ xorl %eax, %eax
\\ ret
\\
);
}
}
// Export helpers - Must be run inside a comptime
fn exportBuiltinFn(comptime func: anytype, comptime func_name: []const u8) void {
@export(func, .{ .name = "roc_builtins." ++ func_name, .linkage = .Strong });

View file

@ -86,6 +86,15 @@ pub fn exportParseFloat(comptime T: type, comptime name: []const u8) void {
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}
pub fn exportNumToFloatCast(comptime T: type, comptime F: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(x: T) callconv(.C) F {
return @floatFromInt(x);
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}
pub fn exportPow(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(base: T, exp: T) callconv(.C) T {
@ -274,42 +283,6 @@ pub fn exportToIntCheckingMaxAndMin(comptime From: type, comptime To: type, comp
@export(f, .{ .name = name ++ @typeName(From), .linkage = .Strong });
}
pub fn bytesToU16C(arg: RocList, position: usize) callconv(.C) u16 {
return @call(.always_inline, bytesToU16, .{ arg, position });
}
fn bytesToU16(arg: RocList, position: usize) u16 {
const bytes = @as([*]const u8, @ptrCast(arg.bytes));
return @as(u16, @bitCast([_]u8{ bytes[position], bytes[position + 1] }));
}
pub fn bytesToU32C(arg: RocList, position: usize) callconv(.C) u32 {
return @call(.always_inline, bytesToU32, .{ arg, position });
}
fn bytesToU32(arg: RocList, position: usize) u32 {
const bytes = @as([*]const u8, @ptrCast(arg.bytes));
return @as(u32, @bitCast([_]u8{ bytes[position], bytes[position + 1], bytes[position + 2], bytes[position + 3] }));
}
pub fn bytesToU64C(arg: RocList, position: usize) callconv(.C) u64 {
return @call(.always_inline, bytesToU64, .{ arg, position });
}
fn bytesToU64(arg: RocList, position: usize) u64 {
const bytes = @as([*]const u8, @ptrCast(arg.bytes));
return @as(u64, @bitCast([_]u8{ bytes[position], bytes[position + 1], bytes[position + 2], bytes[position + 3], bytes[position + 4], bytes[position + 5], bytes[position + 6], bytes[position + 7] }));
}
pub fn bytesToU128C(arg: RocList, position: usize) callconv(.C) u128 {
return @call(.always_inline, bytesToU128, .{ arg, position });
}
fn bytesToU128(arg: RocList, position: usize) u128 {
const bytes = @as([*]const u8, @ptrCast(arg.bytes));
return @as(u128, @bitCast([_]u8{ bytes[position], bytes[position + 1], bytes[position + 2], bytes[position + 3], bytes[position + 4], bytes[position + 5], bytes[position + 6], bytes[position + 7], bytes[position + 8], bytes[position + 9], bytes[position + 10], bytes[position + 11], bytes[position + 12], bytes[position + 13], bytes[position + 14], bytes[position + 15] }));
}
fn isMultipleOf(comptime T: type, lhs: T, rhs: T) bool {
if (rhs == 0 or rhs == -1) {
// lhs is a multiple of rhs iff

View file

@ -1,6 +1,5 @@
const utils = @import("utils.zig");
const RocList = @import("list.zig").RocList;
const grapheme = @import("helpers/grapheme.zig");
const UpdateMode = utils.UpdateMode;
const std = @import("std");
const mem = std.mem;
@ -552,242 +551,6 @@ pub fn strNumberOfBytes(string: RocStr) callconv(.C) usize {
return string.len();
}
// Str.toScalars
pub fn strToScalarsC(str: RocStr) callconv(.C) RocList {
return @call(.always_inline, strToScalars, .{str});
}
fn strToScalars(string: RocStr) callconv(.C) RocList {
const len = string.len();
if (len == 0) {
return RocList.empty();
}
var capacity = len;
if (!string.isSmallStr()) {
capacity = string.getCapacity();
}
// For purposes of preallocation, assume the number of code points is the same
// as the number of bytes. This might be longer than necessary, but definitely
// should not require a second allocation.
var answer = RocList.allocate(@alignOf(u32), capacity, @sizeOf(u32));
// `orelse unreachable` is fine here, because we already did an early
// return to verify the string was nonempty.
var answer_elems = answer.elements(u32) orelse unreachable;
var src_index: usize = 0;
var answer_index: usize = 0;
while (src_index < len) {
src_index += writeNextScalar(string, src_index, answer_elems, answer_index);
answer_index += 1;
}
answer.length = answer_index;
return answer;
}
// Given a non-empty RocStr, and a src_index byte index into that string,
// and a destination [*]u32, and an index into that destination,
// Parses the next scalar value out of the string (at the given byte index),
// writes it into the destination, and returns the number of bytes parsed.
inline fn writeNextScalar(non_empty_string: RocStr, src_index: usize, dest: [*]u32, dest_index: usize) usize {
const utf8_byte = non_empty_string.getUnchecked(src_index);
// How UTF-8 bytes work:
// https://docs.teradata.com/r/Teradata-Database-International-Character-Set-Support/June-2017/Client-Character-Set-Options/UTF8-Client-Character-Set-Support/UTF8-Multibyte-Sequences
if (utf8_byte <= 127) {
// It's an ASCII character. Copy it over directly.
dest[dest_index] = @as(u32, @intCast(utf8_byte));
return 1;
} else if (utf8_byte >> 5 == 0b0000_0110) {
// Its three high order bits are 110, so this is a two-byte sequence.
// Example:
// utf-8: 1100 1111 1011 0001
// code pt: 0000 0011 1111 0001 (decimal: 1009)
// Discard the first byte's high order bits of 110.
var code_pt = @as(u32, @intCast(utf8_byte & 0b0001_1111));
// Discard the second byte's high order bits of 10.
code_pt <<= 6;
code_pt |= non_empty_string.getUnchecked(src_index + 1) & 0b0011_1111;
dest[dest_index] = code_pt;
return 2;
} else if (utf8_byte >> 4 == 0b0000_1110) {
// Its four high order bits are 1110, so this is a three-byte sequence.
// Discard the first byte's high order bits of 1110.
var code_pt = @as(u32, @intCast(utf8_byte & 0b0000_1111));
// Discard the second byte's high order bits of 10.
code_pt <<= 6;
code_pt |= non_empty_string.getUnchecked(src_index + 1) & 0b0011_1111;
// Discard the third byte's high order bits of 10 (same as second byte).
code_pt <<= 6;
code_pt |= non_empty_string.getUnchecked(src_index + 2) & 0b0011_1111;
dest[dest_index] = code_pt;
return 3;
} else {
// This must be a four-byte sequence, so the five high order bits should be 11110.
// Discard the first byte's high order bits of 11110.
var code_pt = @as(u32, @intCast(utf8_byte & 0b0000_0111));
// Discard the second byte's high order bits of 10.
code_pt <<= 6;
code_pt |= non_empty_string.getUnchecked(src_index + 1) & 0b0011_1111;
// Discard the third byte's high order bits of 10 (same as second byte).
code_pt <<= 6;
code_pt |= non_empty_string.getUnchecked(src_index + 2) & 0b0011_1111;
// Discard the fourth byte's high order bits of 10 (same as second and third).
code_pt <<= 6;
code_pt |= non_empty_string.getUnchecked(src_index + 3) & 0b0011_1111;
dest[dest_index] = code_pt;
return 4;
}
}
test "strToScalars: empty string" {
const str = RocStr.fromSlice("");
defer RocStr.decref(str);
const expected = RocList.empty();
const actual = strToScalars(str);
defer actual.decref(@sizeOf(u32));
try expect(RocList.eql(actual, expected));
}
test "strToScalars: One ASCII char" {
const str = RocStr.fromSlice("R");
defer RocStr.decref(str);
const expected_array = [_]u32{82};
const expected = RocList.fromSlice(u32, expected_array[0..expected_array.len]);
defer expected.decref(@sizeOf(u32));
const actual = strToScalars(str);
defer actual.decref(@sizeOf(u32));
try expect(RocList.eql(actual, expected));
}
test "strToScalars: Multiple ASCII chars" {
const str = RocStr.fromSlice("Roc!");
defer RocStr.decref(str);
const expected_array = [_]u32{ 82, 111, 99, 33 };
const expected = RocList.fromSlice(u32, expected_array[0..expected_array.len]);
defer expected.decref(@sizeOf(u32));
const actual = strToScalars(str);
defer actual.decref(@sizeOf(u32));
try expect(RocList.eql(actual, expected));
}
test "strToScalars: One 2-byte UTF-8 character" {
const str = RocStr.fromSlice("é");
defer RocStr.decref(str);
const expected_array = [_]u32{233};
const expected = RocList.fromSlice(u32, expected_array[0..expected_array.len]);
defer expected.decref(@sizeOf(u32));
const actual = strToScalars(str);
defer actual.decref(@sizeOf(u32));
try expect(RocList.eql(actual, expected));
}
test "strToScalars: Multiple 2-byte UTF-8 characters" {
const str = RocStr.fromSlice("Cäfés");
defer RocStr.decref(str);
const expected_array = [_]u32{ 67, 228, 102, 233, 115 };
const expected = RocList.fromSlice(u32, expected_array[0..expected_array.len]);
defer expected.decref(@sizeOf(u32));
const actual = strToScalars(str);
defer actual.decref(@sizeOf(u32));
try expect(RocList.eql(actual, expected));
}
test "strToScalars: One 3-byte UTF-8 character" {
const str = RocStr.fromSlice("");
defer RocStr.decref(str);
const expected_array = [_]u32{40527};
const expected = RocList.fromSlice(u32, expected_array[0..expected_array.len]);
defer expected.decref(@sizeOf(u32));
const actual = strToScalars(str);
defer actual.decref(@sizeOf(u32));
try expect(RocList.eql(actual, expected));
}
test "strToScalars: Multiple 3-byte UTF-8 characters" {
const str = RocStr.fromSlice("鹏很有趣");
defer RocStr.decref(str);
const expected_array = [_]u32{ 40527, 24456, 26377, 36259 };
const expected = RocList.fromSlice(u32, expected_array[0..expected_array.len]);
defer expected.decref(@sizeOf(u32));
const actual = strToScalars(str);
defer actual.decref(@sizeOf(u32));
try expect(RocList.eql(actual, expected));
}
test "strToScalars: One 4-byte UTF-8 character" {
// from https://design215.com/toolbox/utf8-4byte-characters.php
const str = RocStr.fromSlice("𒀀");
defer RocStr.decref(str);
const expected_array = [_]u32{73728};
const expected = RocList.fromSlice(u32, expected_array[0..expected_array.len]);
defer expected.decref(@sizeOf(u32));
const actual = strToScalars(str);
defer actual.decref(@sizeOf(u32));
try expect(RocList.eql(actual, expected));
}
test "strToScalars: Multiple 4-byte UTF-8 characters" {
// from https://design215.com/toolbox/utf8-4byte-characters.php
const str = RocStr.fromSlice("𒀀𒀁");
defer RocStr.decref(str);
const expected_array = [_]u32{ 73728, 73729 };
const expected = RocList.fromSlice(u32, expected_array[0..expected_array.len]);
defer expected.decref(@sizeOf(u32));
const actual = strToScalars(str);
defer actual.decref(@sizeOf(u32));
try expect(RocList.eql(actual, expected));
}
// Str.fromInt
pub fn exportFromInt(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
@ -1371,127 +1134,8 @@ test "countSegments: overlapping delimiter 2" {
try expectEqual(segments_count, 3);
}
// Str.countGraphemeClusters
pub fn countGraphemeClusters(string: RocStr) callconv(.C) usize {
if (string.isEmpty()) {
return 0;
}
const bytes_len = string.len();
const bytes_ptr = string.asU8ptr();
var bytes = bytes_ptr[0..bytes_len];
var iter = (unicode.Utf8View.init(bytes) catch unreachable).iterator();
var count: usize = 0;
var grapheme_break_state: ?grapheme.BoundClass = null;
var grapheme_break_state_ptr = &grapheme_break_state;
var opt_last_codepoint: ?u21 = null;
while (iter.nextCodepoint()) |cur_codepoint| {
if (opt_last_codepoint) |last_codepoint| {
var did_break = grapheme.isGraphemeBreak(last_codepoint, cur_codepoint, grapheme_break_state_ptr);
if (did_break) {
count += 1;
grapheme_break_state = null;
}
}
opt_last_codepoint = cur_codepoint;
}
// If there are no breaks, but the str is not empty, then there
// must be a single grapheme
if (bytes_len != 0) {
count += 1;
}
return count;
}
// Str.graphemes
pub fn strGraphemes(roc_str: RocStr) callconv(.C) RocList {
var break_state: ?grapheme.BoundClass = null;
var opt_last_codepoint: ?u21 = null;
var index: usize = 0;
var last_codepoint_len: u8 = 0;
const alloc_ptr = @intFromPtr(roc_str.getAllocationPtr()) >> 1;
const init_fn = if (roc_str.isSmallStr())
&initFromSmallStr
else
&initFromBigStr;
var result = RocList.allocate(@alignOf(RocStr), countGraphemeClusters(roc_str), @sizeOf(RocStr));
const graphemes = result.elements(RocStr) orelse return result;
var slice = roc_str.asSlice();
var iter = (unicode.Utf8View.init(slice) catch unreachable).iterator();
while (iter.nextCodepoint()) |cur_codepoint| {
const cur_codepoint_len = unicode.utf8CodepointSequenceLength(cur_codepoint) catch unreachable;
if (opt_last_codepoint) |last_codepoint| {
var did_break = grapheme.isGraphemeBreak(last_codepoint, cur_codepoint, &break_state);
if (did_break) {
graphemes[index] = init_fn(@constCast(slice.ptr), last_codepoint_len, alloc_ptr);
slice = slice[last_codepoint_len..];
index += 1;
break_state = null;
last_codepoint_len = 0;
}
}
last_codepoint_len += cur_codepoint_len;
opt_last_codepoint = cur_codepoint;
}
// Append last grapheme
graphemes[index] = init_fn(@constCast(slice.ptr), slice.len, alloc_ptr);
if (!roc_str.isSmallStr()) {
// Correct refcount for all of the splits made.
roc_str.incref(index + 1);
}
return result;
}
// these test both countGraphemeClusters() and strGraphemes()
fn graphemesTest(input: []const u8, expected: []const []const u8) !void {
const rocstr = RocStr.fromSlice(input);
defer rocstr.decref();
const count = countGraphemeClusters(rocstr);
try expectEqual(expected.len, count);
const graphemes = strGraphemes(rocstr);
defer graphemes.decref(@sizeOf(u8));
if (input.len == 0) return; // empty string
const elems = graphemes.elements(RocStr) orelse unreachable;
for (expected, 0..) |g, i| {
try std.testing.expectEqualStrings(g, elems[i].asSlice());
}
}
test "graphemes: empty string" {
try graphemesTest("", &.{});
}
test "graphemes: ascii characters" {
try graphemesTest("abcd", &.{ "a", "b", "c", "d" });
}
test "graphemes: utf8 characters" {
try graphemesTest("ãxā", &.{ "ã", "x", "ā" });
}
test "graphemes: emojis" {
try graphemesTest("🤔🤔🤔", &.{ "🤔", "🤔", "🤔" });
}
test "graphemes: emojis and ut8 characters" {
try graphemesTest("🤔å🤔¥🤔ç", &.{ "🤔", "å", "🤔", "¥", "🤔", "ç" });
}
test "graphemes: emojis, ut8, and ascii characters" {
try graphemesTest("6🤔å🤔e¥🤔çpp", &.{ "6", "🤔", "å", "🤔", "e", "¥", "🤔", "ç", "p", "p" });
}
pub fn countUtf8Bytes(string: RocStr) callconv(.C) usize {
return string.len();
pub fn countUtf8Bytes(string: RocStr) callconv(.C) u64 {
return @intCast(string.len());
}
pub fn isEmpty(string: RocStr) callconv(.C) bool {
@ -1502,7 +1146,14 @@ pub fn getCapacity(string: RocStr) callconv(.C) usize {
return string.getCapacity();
}
pub fn substringUnsafe(string: RocStr, start: usize, length: usize) callconv(.C) RocStr {
pub fn substringUnsafeC(string: RocStr, start_u64: u64, length_u64: u64) callconv(.C) RocStr {
const start: usize = @intCast(start_u64);
const length: usize = @intCast(length_u64);
return substringUnsafe(string, start, length);
}
fn substringUnsafe(string: RocStr, start: usize, length: usize) RocStr {
if (string.isSmallStr()) {
if (start == 0) {
var output = string;
@ -1534,8 +1185,8 @@ pub fn substringUnsafe(string: RocStr, start: usize, length: usize) callconv(.C)
return RocStr.empty();
}
pub fn getUnsafe(string: RocStr, index: usize) callconv(.C) u8 {
return string.getUnchecked(index);
pub fn getUnsafeC(string: RocStr, index: u64) callconv(.C) u8 {
return string.getUnchecked(@intCast(index));
}
test "substringUnsafe: start" {
@ -1598,7 +1249,8 @@ pub fn startsWith(string: RocStr, prefix: RocStr) callconv(.C) bool {
}
// Str.repeat
pub fn repeat(string: RocStr, count: usize) callconv(.C) RocStr {
pub fn repeatC(string: RocStr, count_u64: u64) callconv(.C) RocStr {
const count: usize = @intCast(count_u64);
const bytes_len = string.len();
const bytes_ptr = string.asU8ptr();
@ -1614,44 +1266,6 @@ pub fn repeat(string: RocStr, count: usize) callconv(.C) RocStr {
return ret_string;
}
// Str.startsWithScalar
pub fn startsWithScalar(string: RocStr, prefix: u32) callconv(.C) bool {
const len = string.len();
if (len == 0) {
return false;
}
// Write this (non-empty) string's first scalar into `first_scalar`
var first_scalar: [1]u32 = undefined;
_ = writeNextScalar(string, 0, &first_scalar, 0);
// Return whether `first_scalar` equals `prefix`
return @as(*u32, @ptrCast(&first_scalar)).* == prefix;
}
test "startsWithScalar: empty string" {
const whole = RocStr.empty();
const prefix: u32 = 'x';
try expect(!startsWithScalar(whole, prefix));
}
test "startsWithScalar: ascii char" {
const whole = RocStr.fromSlice("foobar");
const prefix: u32 = 'f';
try expect(startsWithScalar(whole, prefix));
}
test "startsWithScalar: emoji" {
const yes = RocStr.fromSlice("💖foobar");
const no = RocStr.fromSlice("foobar");
const prefix: u32 = '💖';
try expect(startsWithScalar(yes, prefix));
try expect(!startsWithScalar(no, prefix));
}
test "startsWith: foo starts with fo" {
const foo = RocStr.fromSlice("foo");
const fo = RocStr.fromSlice("fo");
@ -1891,29 +1505,25 @@ inline fn strToBytes(arg: RocStr) RocList {
}
const FromUtf8Result = extern struct {
byte_index: usize,
byte_index: u64,
string: RocStr,
is_ok: bool,
problem_code: Utf8ByteProblem,
};
const CountAndStart = extern struct {
count: usize,
start: usize,
};
pub fn fromUtf8RangeC(
pub fn fromUtf8C(
list: RocList,
start: usize,
count: usize,
update_mode: UpdateMode,
) callconv(.C) FromUtf8Result {
return fromUtf8Range(list, start, count, update_mode);
return fromUtf8(list, update_mode);
}
pub fn fromUtf8Range(arg: RocList, start: usize, count: usize, update_mode: UpdateMode) FromUtf8Result {
if (arg.len() == 0 or count == 0) {
arg.decref(RocStr.alignment);
pub fn fromUtf8(
list: RocList,
update_mode: UpdateMode,
) FromUtf8Result {
if (list.len() == 0) {
list.decref(1); // Alignment 1 for List U8
return FromUtf8Result{
.is_ok = true,
.string = RocStr.empty(),
@ -1921,11 +1531,11 @@ pub fn fromUtf8Range(arg: RocList, start: usize, count: usize, update_mode: Upda
.problem_code = Utf8ByteProblem.InvalidStartByte,
};
}
const bytes = @as([*]const u8, @ptrCast(arg.bytes))[start .. start + count];
const bytes = @as([*]const u8, @ptrCast(list.bytes))[0..list.len()];
if (isValidUnicode(bytes)) {
// Make a seamless slice of the input.
const string = RocStr.fromSubListUnsafe(arg, start, count, update_mode);
const string = RocStr.fromSubListUnsafe(list, 0, list.len(), update_mode);
return FromUtf8Result{
.is_ok = true,
.string = string,
@ -1933,25 +1543,25 @@ pub fn fromUtf8Range(arg: RocList, start: usize, count: usize, update_mode: Upda
.problem_code = Utf8ByteProblem.InvalidStartByte,
};
} else {
const temp = errorToProblem(@as([*]u8, @ptrCast(arg.bytes)), arg.length);
const temp = errorToProblem(bytes);
// decref the list
arg.decref(RocStr.alignment);
list.decref(1); // Alignment 1 for List U8
return FromUtf8Result{
.is_ok = false,
.string = RocStr.empty(),
.byte_index = temp.index,
.byte_index = @intCast(temp.index),
.problem_code = temp.problem,
};
}
}
fn errorToProblem(bytes: [*]u8, length: usize) struct { index: usize, problem: Utf8ByteProblem } {
fn errorToProblem(bytes: []const u8) struct { index: usize, problem: Utf8ByteProblem } {
const len = bytes.len;
var index: usize = 0;
while (index < length) {
const nextNumBytes = numberOfNextCodepointBytes(bytes, length, index) catch |err| {
while (index < len) {
const nextNumBytes = numberOfNextCodepointBytes(bytes, index) catch |err| {
switch (err) {
error.UnexpectedEof => {
return .{ .index = index, .problem = Utf8ByteProblem.UnexpectedEndOfSequence };
@ -2025,13 +1635,13 @@ const Utf8DecodeError = error{
// Essentially unicode.utf8ValidateSlice -> https://github.com/ziglang/zig/blob/0.7.x/lib/std/unicode.zig#L156
// but only for the next codepoint from the index. Then we return the number of bytes of that codepoint.
// TODO: we only ever use the values 0-4, so can we use smaller int than `usize`?
pub fn numberOfNextCodepointBytes(ptr: [*]u8, len: usize, index: usize) Utf8DecodeError!usize {
const codepoint_len = try unicode.utf8ByteSequenceLength(ptr[index]);
pub fn numberOfNextCodepointBytes(bytes: []const u8, index: usize) Utf8DecodeError!usize {
const codepoint_len = try unicode.utf8ByteSequenceLength(bytes[index]);
const codepoint_end_index = index + codepoint_len;
if (codepoint_end_index > len) {
if (codepoint_end_index > bytes.len) {
return error.UnexpectedEof;
}
_ = try unicode.utf8Decode(ptr[index..codepoint_end_index]);
_ = try unicode.utf8Decode(bytes[index..codepoint_end_index]);
return codepoint_end_index - index;
}
@ -2047,11 +1657,11 @@ pub const Utf8ByteProblem = enum(u8) {
};
fn validateUtf8Bytes(bytes: [*]u8, length: usize) FromUtf8Result {
return fromUtf8Range(RocList{ .bytes = bytes, .length = length, .capacity_or_alloc_ptr = length }, 0, length, .Immutable);
return fromUtf8(RocList{ .bytes = bytes, .length = length, .capacity_or_alloc_ptr = length }, .Immutable);
}
fn validateUtf8BytesX(str: RocList) FromUtf8Result {
return fromUtf8Range(str, 0, str.len(), .Immutable);
return fromUtf8(str, .Immutable);
}
fn expectOk(result: FromUtf8Result) !void {
@ -2068,7 +1678,7 @@ fn sliceHelp(bytes: [*]const u8, length: usize) RocList {
}
fn toErrUtf8ByteResponse(index: usize, problem: Utf8ByteProblem) FromUtf8Result {
return FromUtf8Result{ .is_ok = false, .string = RocStr.empty(), .byte_index = index, .problem_code = problem };
return FromUtf8Result{ .is_ok = false, .string = RocStr.empty(), .byte_index = @as(u64, @intCast(index)), .problem_code = problem };
}
// NOTE on memory: the validate function consumes a RC token of the input. Since
@ -2130,7 +1740,7 @@ fn expectErr(list: RocList, index: usize, err: Utf8DecodeError, problem: Utf8Byt
const str_ptr = @as([*]u8, @ptrCast(list.bytes));
const len = list.length;
try expectError(err, numberOfNextCodepointBytes(str_ptr, len, index));
try expectError(err, numberOfNextCodepointBytes(str_ptr[0..len], index));
try expectEqual(toErrUtf8ByteResponse(index, problem), validateUtf8Bytes(str_ptr, len));
}
@ -2761,80 +2371,13 @@ test "capacity: big string" {
try expect(data.getCapacity() >= data_bytes.len);
}
pub fn appendScalar(string: RocStr, scalar_u32: u32) callconv(.C) RocStr {
const scalar = @as(u21, @intCast(scalar_u32));
const width = std.unicode.utf8CodepointSequenceLength(scalar) catch unreachable;
var output = string.reallocate(string.len() + width);
var slice = output.asSliceWithCapacityMut();
_ = std.unicode.utf8Encode(scalar, slice[string.len() .. string.len() + width]) catch unreachable;
return output;
pub fn reserveC(string: RocStr, spare_u64: u64) callconv(.C) RocStr {
return reserve(string, @intCast(spare_u64));
}
test "appendScalar: small A" {
const A: []const u8 = "A";
const data_bytes = "hello";
var data = RocStr.init(data_bytes, data_bytes.len);
const actual = appendScalar(data, A[0]);
defer actual.decref();
const expected_bytes = "helloA";
const expected = RocStr.init(expected_bytes, expected_bytes.len);
defer expected.decref();
try expect(actual.eq(expected));
}
test "appendScalar: small 😀" {
const data_bytes = "hello";
var data = RocStr.init(data_bytes, data_bytes.len);
const actual = appendScalar(data, 0x1F600);
defer actual.decref();
const expected_bytes = "hello😀";
const expected = RocStr.init(expected_bytes, expected_bytes.len);
defer expected.decref();
try expect(actual.eq(expected));
}
test "appendScalar: big A" {
const A: []const u8 = "A";
const data_bytes = "a string so large that it must be heap-allocated";
var data = RocStr.init(data_bytes, data_bytes.len);
const actual = appendScalar(data, A[0]);
defer actual.decref();
const expected_bytes = "a string so large that it must be heap-allocatedA";
const expected = RocStr.init(expected_bytes, expected_bytes.len);
defer expected.decref();
try expect(actual.eq(expected));
}
test "appendScalar: big 😀" {
const data_bytes = "a string so large that it must be heap-allocated";
var data = RocStr.init(data_bytes, data_bytes.len);
const actual = appendScalar(data, 0x1F600);
defer actual.decref();
const expected_bytes = "a string so large that it must be heap-allocated😀";
const expected = RocStr.init(expected_bytes, expected_bytes.len);
defer expected.decref();
try expect(actual.eq(expected));
}
pub fn reserve(string: RocStr, spare: usize) callconv(.C) RocStr {
fn reserve(string: RocStr, spare: usize) RocStr {
const old_length = string.len();
if (string.getCapacity() >= old_length + spare) {
return string;
} else {
@ -2844,32 +2387,12 @@ pub fn reserve(string: RocStr, spare: usize) callconv(.C) RocStr {
}
}
pub fn withCapacity(capacity: usize) callconv(.C) RocStr {
var str = RocStr.allocate(capacity);
pub fn withCapacityC(capacity: u64) callconv(.C) RocStr {
var str = RocStr.allocate(@intCast(capacity));
str.setLen(0);
return str;
}
pub fn getScalarUnsafe(string: RocStr, index: usize) callconv(.C) extern struct { bytesParsed: usize, scalar: u32 } {
const slice = string.asSlice();
const bytesParsed = @as(usize, @intCast(std.unicode.utf8ByteSequenceLength(slice[index]) catch unreachable));
const scalar = std.unicode.utf8Decode(slice[index .. index + bytesParsed]) catch unreachable;
return .{ .bytesParsed = bytesParsed, .scalar = @as(u32, @intCast(scalar)) };
}
test "getScalarUnsafe" {
const data_bytes = "A";
var data = RocStr.init(data_bytes, data_bytes.len);
const result = getScalarUnsafe(data, 0);
const expected = try std.unicode.utf8Decode("A");
try expectEqual(result.scalar, @as(u32, @intCast(expected)));
try expectEqual(result.bytesParsed, 1);
}
pub fn strCloneTo(
string: RocStr,
ptr: [*]u8,

View file

@ -44,7 +44,6 @@ interface Decode
I32,
I64,
I128,
Nat,
F32,
F64,
Dec,
@ -113,7 +112,7 @@ DecoderFormatting implements
## index passed to `stepElem` is 0-indexed.
##
## `finalizer` should produce the tuple value from the decoded `state`.
tuple : state, (state, Nat -> [Next (Decoder state fmt), TooLong]), (state -> Result val DecodeError) -> Decoder val fmt where fmt implements DecoderFormatting
tuple : state, (state, U64 -> [Next (Decoder state fmt), TooLong]), (state -> Result val DecodeError) -> Decoder val fmt where fmt implements DecoderFormatting
## Build a custom [Decoder] function. For example the implementation of
## `decodeBool` could be defined as follows;

View file

@ -34,7 +34,7 @@ interface Dict
Result.{ Result },
List,
Str,
Num.{ Nat, U64, F32, U32, U8, I8 },
Num.{ U64, F32, U32, U8, I8 },
Hash.{ Hasher, Hash },
Inspect.{ Inspect, Inspector, InspectFormatter },
]
@ -151,26 +151,25 @@ empty = \{} ->
## Return a dictionary with space allocated for a number of entries. This
## may provide a performance optimization if you know how many entries will be
## inserted.
withCapacity : Nat -> Dict * *
withCapacity : U64 -> Dict * *
withCapacity = \requested ->
empty {}
|> reserve requested
## Enlarge the dictionary for at least capacity additional elements
reserve : Dict k v, Nat -> Dict k v
reserve : Dict k v, U64 -> Dict k v
reserve = \@Dict { buckets, data, maxBucketCapacity: originalMaxBucketCapacity, maxLoadFactor, shifts }, requested ->
currentSize = List.len data
requestedSize = currentSize + requested
size = Num.min (Num.toU64 requestedSize) maxSize
requestedSize = Num.addWrap currentSize requested
size = Num.min requestedSize maxSize
requestedShifts = calcShiftsForSize size maxLoadFactor
if (List.isEmpty buckets) || requestedShifts > shifts then
(buckets0, maxBucketCapacity) = allocBucketsFromShift requestedShifts maxLoadFactor
buckets1 = fillBucketsFromData buckets0 data requestedShifts
sizeNat = Num.toNat size
@Dict {
buckets: buckets1,
data: List.reserve data (Num.subSaturated sizeNat currentSize),
data: List.reserve data (Num.subSaturated size currentSize),
maxBucketCapacity,
maxLoadFactor,
shifts: requestedShifts,
@ -186,7 +185,7 @@ releaseExcessCapacity = \@Dict { buckets, data, maxBucketCapacity: originalMaxBu
size = List.len data
# NOTE: If we want, we technically could increase the load factor here to potentially minimize size more.
minShifts = calcShiftsForSize (Num.toU64 size) maxLoadFactor
minShifts = calcShiftsForSize size maxLoadFactor
if minShifts < shifts then
(buckets0, maxBucketCapacity) = allocBucketsFromShift minShifts maxLoadFactor
buckets1 = fillBucketsFromData buckets0 data minShifts
@ -208,9 +207,9 @@ releaseExcessCapacity = \@Dict { buckets, data, maxBucketCapacity: originalMaxBu
##
## capacityOfDict = Dict.capacity foodDict
## ```
capacity : Dict * * -> Nat
capacity : Dict * * -> U64
capacity = \@Dict { maxBucketCapacity } ->
Num.toNat maxBucketCapacity
maxBucketCapacity
## Returns a dictionary containing the key and value provided as input.
## ```
@ -251,7 +250,7 @@ fromList = \data ->
## |> Dict.len
## |> Bool.isEq 3
## ```
len : Dict * * -> Nat
len : Dict * * -> U64
len = \@Dict { data } ->
List.len data
@ -372,14 +371,14 @@ keepIf : Dict k v, ((k, v) -> Bool) -> Dict k v
keepIf = \dict, predicate ->
keepIfHelp dict predicate 0 (Dict.len dict)
keepIfHelp : Dict k v, ((k, v) -> Bool), Nat, Nat -> Dict k v
keepIfHelp : Dict k v, ((k, v) -> Bool), U64, U64 -> Dict k v
keepIfHelp = \@Dict dict, predicate, index, length ->
if index < length then
(key, value) = listGetUnsafe dict.data index
if predicate (key, value) then
keepIfHelp (@Dict dict) predicate (index + 1) length
keepIfHelp (@Dict dict) predicate (index |> Num.addWrap 1) length
else
keepIfHelp (Dict.remove (@Dict dict) key) predicate index (length - 1)
keepIfHelp (Dict.remove (@Dict dict) key) predicate index (length |> Num.subWrap 1)
else
@Dict dict
@ -450,13 +449,13 @@ insert = \dict, key, value ->
insertHelper buckets data bucketIndex distAndFingerprint key value maxBucketCapacity maxLoadFactor shifts
insertHelper : List Bucket, List (k, v), Nat, U32, k, v, U64, F32, U8 -> Dict k v
insertHelper : List Bucket, List (k, v), U64, U32, k, v, U64, F32, U8 -> Dict k v
insertHelper = \buckets0, data0, bucketIndex0, distAndFingerprint0, key, value, maxBucketCapacity, maxLoadFactor, shifts ->
loaded = listGetUnsafe buckets0 (Num.toNat bucketIndex0)
loaded = listGetUnsafe buckets0 bucketIndex0
if distAndFingerprint0 == loaded.distAndFingerprint then
(foundKey, _) = listGetUnsafe data0 (Num.toNat loaded.dataIndex)
(foundKey, _) = listGetUnsafe data0 (Num.toU64 loaded.dataIndex)
if foundKey == key then
data1 = List.set data0 (Num.toNat loaded.dataIndex) (key, value)
data1 = List.set data0 (Num.toU64 loaded.dataIndex) (key, value)
@Dict { buckets: buckets0, data: data1, maxBucketCapacity, maxLoadFactor, shifts }
else
bucketIndex1 = nextBucketIndex bucketIndex0 (List.len buckets0)
@ -464,7 +463,7 @@ insertHelper = \buckets0, data0, bucketIndex0, distAndFingerprint0, key, value,
insertHelper buckets0 data0 bucketIndex1 distAndFingerprint1 key value maxBucketCapacity maxLoadFactor shifts
else if distAndFingerprint0 > loaded.distAndFingerprint then
data1 = List.append data0 (key, value)
dataIndex = (List.len data1) - 1
dataIndex = (List.len data1) |> Num.subWrap 1
buckets1 = placeAndShiftUp buckets0 { distAndFingerprint: distAndFingerprint0, dataIndex: Num.toU32 dataIndex } bucketIndex0
@Dict { buckets: buckets1, data: data1, maxBucketCapacity, maxLoadFactor, shifts }
else
@ -487,7 +486,7 @@ remove = \@Dict { buckets, data, maxBucketCapacity, maxLoadFactor, shifts }, key
(bucketIndex0, distAndFingerprint0) = nextWhileLess buckets key shifts
(bucketIndex1, distAndFingerprint1) = removeHelper buckets bucketIndex0 distAndFingerprint0 data key
bucket = listGetUnsafe buckets (Num.toNat bucketIndex1)
bucket = listGetUnsafe buckets bucketIndex1
if distAndFingerprint1 != bucket.distAndFingerprint then
@Dict { buckets, data, maxBucketCapacity, maxLoadFactor, shifts }
else
@ -495,10 +494,11 @@ remove = \@Dict { buckets, data, maxBucketCapacity, maxLoadFactor, shifts }, key
else
@Dict { buckets, data, maxBucketCapacity, maxLoadFactor, shifts }
removeHelper : List Bucket, U64, U32, List (k, *), k -> (U64, U32) where k implements Eq
removeHelper = \buckets, bucketIndex, distAndFingerprint, data, key ->
bucket = listGetUnsafe buckets (Num.toNat bucketIndex)
bucket = listGetUnsafe buckets bucketIndex
if distAndFingerprint == bucket.distAndFingerprint then
(foundKey, _) = listGetUnsafe data (Num.toNat bucket.dataIndex)
(foundKey, _) = listGetUnsafe data (Num.toU64 bucket.dataIndex)
if foundKey == key then
(bucketIndex, distAndFingerprint)
else
@ -529,7 +529,7 @@ update = \@Dict { buckets, data, maxBucketCapacity, maxLoadFactor, shifts }, key
when alter (Present value) is
Present newValue ->
bucket = listGetUnsafe buckets bucketIndex
newData = List.set data (Num.toNat bucket.dataIndex) (key, newValue)
newData = List.set data (Num.toU64 bucket.dataIndex) (key, newValue)
@Dict { buckets, data: newData, maxBucketCapacity, maxLoadFactor, shifts }
Missing ->
@ -538,7 +538,7 @@ update = \@Dict { buckets, data, maxBucketCapacity, maxLoadFactor, shifts }, key
Err KeyNotFound ->
when alter Missing is
Present newValue ->
if List.len data >= (Num.toNat maxBucketCapacity) then
if List.len data >= maxBucketCapacity then
# Need to reallocate let regular insert handle that.
insert (@Dict { buckets, data, maxBucketCapacity, maxLoadFactor, shifts }) key newValue
else
@ -721,20 +721,20 @@ emptyBucket = { distAndFingerprint: 0, dataIndex: 0 }
distInc = Num.shiftLeftBy 1u32 8 # skip 1 byte fingerprint
fingerprintMask = Num.subWrap distInc 1 # mask for 1 byte of fingerprint
defaultMaxLoadFactor = 0.8
initialShifts = 64 - 3 # 2^(64-shifts) number of buckets
initialShifts = 64 |> Num.subWrap 3 # 2^(64-shifts) number of buckets
maxSize = Num.shiftLeftBy 1u64 32
maxBucketCount = maxSize
incrementDist = \distAndFingerprint ->
distAndFingerprint + distInc
Num.addWrap distAndFingerprint distInc
incrementDistN = \distAndFingerprint, n ->
distAndFingerprint + (n * distInc)
Num.addWrap distAndFingerprint (Num.mulWrap n distInc)
decrementDist = \distAndFingerprint ->
distAndFingerprint - distInc
distAndFingerprint |> Num.subWrap distInc
find : Dict k v, k -> { bucketIndex : Nat, result : Result v [KeyNotFound] }
find : Dict k v, k -> { bucketIndex : U64, result : Result v [KeyNotFound] }
find = \@Dict { buckets, data, shifts }, key ->
hash = hashKey key
distAndFingerprint = distAndFingerprintFromHash hash
@ -749,13 +749,13 @@ find = \@Dict { buckets, data, shifts }, key ->
findManualUnrolls = 2
findFirstUnroll : List Bucket, Nat, U32, List (k, v), k -> { bucketIndex : Nat, result : Result v [KeyNotFound] } where k implements Eq
findFirstUnroll : List Bucket, U64, U32, List (k, v), k -> { bucketIndex : U64, result : Result v [KeyNotFound] } where k implements Eq
findFirstUnroll = \buckets, bucketIndex, distAndFingerprint, data, key ->
# TODO: once we have short circuit evaluation, use it here and other similar locations in this file.
# Avoid the nested if with else block inconvenience.
bucket = listGetUnsafe buckets bucketIndex
if distAndFingerprint == bucket.distAndFingerprint then
(foundKey, value) = listGetUnsafe data (Num.toNat bucket.dataIndex)
(foundKey, value) = listGetUnsafe data (Num.toU64 bucket.dataIndex)
if foundKey == key then
{ bucketIndex, result: Ok value }
else
@ -763,11 +763,11 @@ findFirstUnroll = \buckets, bucketIndex, distAndFingerprint, data, key ->
else
findSecondUnroll buckets (nextBucketIndex bucketIndex (List.len buckets)) (incrementDist distAndFingerprint) data key
findSecondUnroll : List Bucket, Nat, U32, List (k, v), k -> { bucketIndex : Nat, result : Result v [KeyNotFound] } where k implements Eq
findSecondUnroll : List Bucket, U64, U32, List (k, v), k -> { bucketIndex : U64, result : Result v [KeyNotFound] } where k implements Eq
findSecondUnroll = \buckets, bucketIndex, distAndFingerprint, data, key ->
bucket = listGetUnsafe buckets bucketIndex
if distAndFingerprint == bucket.distAndFingerprint then
(foundKey, value) = listGetUnsafe data (Num.toNat bucket.dataIndex)
(foundKey, value) = listGetUnsafe data (Num.toU64 bucket.dataIndex)
if foundKey == key then
{ bucketIndex, result: Ok value }
else
@ -775,11 +775,11 @@ findSecondUnroll = \buckets, bucketIndex, distAndFingerprint, data, key ->
else
findHelper buckets (nextBucketIndex bucketIndex (List.len buckets)) (incrementDist distAndFingerprint) data key
findHelper : List Bucket, Nat, U32, List (k, v), k -> { bucketIndex : Nat, result : Result v [KeyNotFound] } where k implements Eq
findHelper : List Bucket, U64, U32, List (k, v), k -> { bucketIndex : U64, result : Result v [KeyNotFound] } where k implements Eq
findHelper = \buckets, bucketIndex, distAndFingerprint, data, key ->
bucket = listGetUnsafe buckets bucketIndex
if distAndFingerprint == bucket.distAndFingerprint then
(foundKey, value) = listGetUnsafe data (Num.toNat bucket.dataIndex)
(foundKey, value) = listGetUnsafe data (Num.toU64 bucket.dataIndex)
if foundKey == key then
{ bucketIndex, result: Ok value }
else
@ -789,18 +789,19 @@ findHelper = \buckets, bucketIndex, distAndFingerprint, data, key ->
else
findHelper buckets (nextBucketIndex bucketIndex (List.len buckets)) (incrementDist distAndFingerprint) data key
removeBucket : Dict k v, Nat -> Dict k v
removeBucket : Dict k v, U64 -> Dict k v
removeBucket = \@Dict { buckets: buckets0, data: data0, maxBucketCapacity, maxLoadFactor, shifts }, bucketIndex0 ->
{ dataIndex: dataIndexToRemove } = listGetUnsafe buckets0 bucketIndex0
dataIndexToRemove = (listGetUnsafe buckets0 bucketIndex0).dataIndex
dataIndexToRemoveU64 = Num.toU64 dataIndexToRemove
(buckets1, bucketIndex1) = removeBucketHelper buckets0 bucketIndex0
buckets2 = List.set buckets1 bucketIndex1 emptyBucket
lastDataIndex = List.len data0 - 1
if (Num.toNat dataIndexToRemove) != lastDataIndex then
lastDataIndex = List.len data0 |> Num.subWrap 1
if dataIndexToRemoveU64 != lastDataIndex then
# Swap removed item to the end
data1 = List.swap data0 (Num.toNat dataIndexToRemove) lastDataIndex
(key, _) = listGetUnsafe data1 (Num.toNat dataIndexToRemove)
data1 = List.swap data0 dataIndexToRemoveU64 lastDataIndex
(key, _) = listGetUnsafe data1 dataIndexToRemoveU64
# Update the data index of the new value.
hash = hashKey key
@ -824,7 +825,7 @@ removeBucket = \@Dict { buckets: buckets0, data: data0, maxBucketCapacity, maxLo
shifts,
}
scanForIndex : List Bucket, Nat, U32 -> Nat
scanForIndex : List Bucket, U64, U32 -> U64
scanForIndex = \buckets, bucketIndex, dataIndex ->
bucket = listGetUnsafe buckets bucketIndex
if bucket.dataIndex != dataIndex then
@ -832,12 +833,12 @@ scanForIndex = \buckets, bucketIndex, dataIndex ->
else
bucketIndex
removeBucketHelper : List Bucket, Nat -> (List Bucket, Nat)
removeBucketHelper : List Bucket, U64 -> (List Bucket, U64)
removeBucketHelper = \buckets, bucketIndex ->
nextIndex = nextBucketIndex bucketIndex (List.len buckets)
nextBucket = listGetUnsafe buckets nextIndex
# shift down until either empty or an element with correct spot is found
if nextBucket.distAndFingerprint >= distInc * 2 then
if nextBucket.distAndFingerprint >= Num.mulWrap distInc 2 then
List.set buckets bucketIndex { nextBucket & distAndFingerprint: decrementDist nextBucket.distAndFingerprint }
|> removeBucketHelper nextIndex
else
@ -846,7 +847,7 @@ removeBucketHelper = \buckets, bucketIndex ->
increaseSize : Dict k v -> Dict k v
increaseSize = \@Dict { data, maxBucketCapacity, maxLoadFactor, shifts } ->
if maxBucketCapacity != maxBucketCount then
newShifts = shifts - 1
newShifts = shifts |> Num.subWrap 1
(buckets0, newMaxBucketCapacity) = allocBucketsFromShift newShifts maxLoadFactor
buckets1 = fillBucketsFromData buckets0 data newShifts
@Dict {
@ -857,21 +858,21 @@ increaseSize = \@Dict { data, maxBucketCapacity, maxLoadFactor, shifts } ->
shifts: newShifts,
}
else
crash "Dict hit limit of \(Num.toStr maxBucketCount) elements. Unable to grow more."
crash "Dict hit limit of $(Num.toStr maxBucketCount) elements. Unable to grow more."
allocBucketsFromShift : U8, F32 -> (List Bucket, U64)
allocBucketsFromShift = \shifts, maxLoadFactor ->
bucketCount = calcNumBuckets shifts
if bucketCount == maxBucketCount then
# reached the maximum, make sure we can use each bucket
(List.repeat emptyBucket (Num.toNat maxBucketCount), maxBucketCount)
(List.repeat emptyBucket maxBucketCount, maxBucketCount)
else
maxBucketCapacity =
bucketCount
|> Num.toF32
|> Num.mul maxLoadFactor
|> Num.floor
(List.repeat emptyBucket (Num.toNat bucketCount), maxBucketCapacity)
(List.repeat emptyBucket bucketCount, maxBucketCapacity)
calcShiftsForSize : U64, F32 -> U8
calcShiftsForSize = \size, maxLoadFactor ->
@ -885,13 +886,13 @@ calcShiftsForSizeHelper = \shifts, size, maxLoadFactor ->
|> Num.mul maxLoadFactor
|> Num.floor
if shifts > 0 && maxBucketCapacity < size then
calcShiftsForSizeHelper (shifts - 1) size maxLoadFactor
calcShiftsForSizeHelper (shifts |> Num.subWrap 1) size maxLoadFactor
else
shifts
calcNumBuckets = \shifts ->
Num.min
(Num.shiftLeftBy 1 (64 - shifts))
(Num.shiftLeftBy 1 (64 |> Num.subWrap shifts))
maxBucketCount
fillBucketsFromData = \buckets0, data, shifts ->
@ -899,7 +900,7 @@ fillBucketsFromData = \buckets0, data, shifts ->
(bucketIndex, distAndFingerprint) = nextWhileLess buckets1 key shifts
placeAndShiftUp buckets1 { distAndFingerprint, dataIndex: Num.toU32 dataIndex } bucketIndex
nextWhileLess : List Bucket, k, U8 -> (Nat, U32) where k implements Hash & Eq
nextWhileLess : List Bucket, k, U8 -> (U64, U32) where k implements Hash & Eq
nextWhileLess = \buckets, key, shifts ->
hash = hashKey key
distAndFingerprint = distAndFingerprintFromHash hash
@ -908,22 +909,22 @@ nextWhileLess = \buckets, key, shifts ->
nextWhileLessHelper buckets bucketIndex distAndFingerprint
nextWhileLessHelper = \buckets, bucketIndex, distAndFingerprint ->
loaded = listGetUnsafe buckets (Num.toNat bucketIndex)
loaded = listGetUnsafe buckets bucketIndex
if distAndFingerprint < loaded.distAndFingerprint then
nextWhileLessHelper buckets (nextBucketIndex bucketIndex (List.len buckets)) (incrementDist distAndFingerprint)
else
(bucketIndex, distAndFingerprint)
placeAndShiftUp = \buckets0, bucket, bucketIndex ->
loaded = listGetUnsafe buckets0 (Num.toNat bucketIndex)
loaded = listGetUnsafe buckets0 bucketIndex
if loaded.distAndFingerprint != 0 then
buckets1 = List.set buckets0 (Num.toNat bucketIndex) bucket
buckets1 = List.set buckets0 bucketIndex bucket
placeAndShiftUp
buckets1
{ loaded & distAndFingerprint: incrementDist loaded.distAndFingerprint }
(nextBucketIndex bucketIndex (List.len buckets1))
else
List.set buckets0 (Num.toNat bucketIndex) bucket
List.set buckets0 bucketIndex bucket
nextBucketIndex = \bucketIndex, maxBuckets ->
# I just ported this impl directly.
@ -947,11 +948,10 @@ distAndFingerprintFromHash = \hash ->
|> Num.bitwiseAnd fingerprintMask
|> Num.bitwiseOr distInc
bucketIndexFromHash : U64, U8 -> Nat
bucketIndexFromHash : U64, U8 -> U64
bucketIndexFromHash = \hash, shifts ->
hash
|> Num.shiftRightZfBy shifts
|> Num.toNat
expect
val =
@ -1185,13 +1185,6 @@ expect
|> len
|> Bool.isEq 0
# Makes sure a Dict with Nat keys works
expect
empty {}
|> insert 7nat "Testing"
|> get 7
|> Bool.isEq (Ok "Testing")
# All BadKey's hash to the same location.
# This is needed to test some robinhood logic.
BadKey := U64 implements [
@ -1225,7 +1218,7 @@ expect
acc, k <- List.walk badKeys (Dict.empty {})
Dict.update acc k \val ->
when val is
Present p -> Present (p + 1)
Present p -> Present (p |> Num.addWrap 1)
Missing -> Present 0
allInsertedCorrectly =
@ -1236,7 +1229,7 @@ expect
# Note, there are a number of places we should probably use set and replace unsafe.
# unsafe primitive that does not perform a bounds check
listGetUnsafe : List a, Nat -> a
listGetUnsafe : List a, U64 -> a
# We have decided not to expose the standard roc hashing algorithm.
# This is to avoid external dependence and the need for versioning.
@ -1368,9 +1361,9 @@ addBytes = \@LowLevelHasher { initializedSeed, state }, list ->
else
hashBytesHelper48 initializedSeed initializedSeed initializedSeed list 0 length
combineState (@LowLevelHasher { initializedSeed, state }) { a: abs.a, b: abs.b, seed: abs.seed, length: Num.toU64 length }
combineState (@LowLevelHasher { initializedSeed, state }) { a: abs.a, b: abs.b, seed: abs.seed, length }
hashBytesHelper48 : U64, U64, U64, List U8, Nat, Nat -> { a : U64, b : U64, seed : U64 }
hashBytesHelper48 : U64, U64, U64, List U8, U64, U64 -> { a : U64, b : U64, seed : U64 }
hashBytesHelper48 = \seed, see1, see2, list, index, remaining ->
newSeed = wymix (Num.bitwiseXor (wyr8 list index) wyp1) (Num.bitwiseXor (wyr8 list (Num.addWrap index 8)) seed)
newSee1 = wymix (Num.bitwiseXor (wyr8 list (Num.addWrap index 16)) wyp2) (Num.bitwiseXor (wyr8 list (Num.addWrap index 24)) see1)
@ -1389,7 +1382,7 @@ hashBytesHelper48 = \seed, see1, see2, list, index, remaining ->
{ a: wyr8 list (Num.subWrap newRemaining 16 |> Num.addWrap newIndex), b: wyr8 list (Num.subWrap newRemaining 8 |> Num.addWrap newIndex), seed: finalSeed }
hashBytesHelper16 : U64, List U8, Nat, Nat -> { a : U64, b : U64, seed : U64 }
hashBytesHelper16 : U64, List U8, U64, U64 -> { a : U64, b : U64, seed : U64 }
hashBytesHelper16 = \seed, list, index, remaining ->
newSeed = wymix (Num.bitwiseXor (wyr8 list index) wyp1) (Num.bitwiseXor (wyr8 list (Num.addWrap index 8)) seed)
newRemaining = Num.subWrap remaining 16
@ -1417,7 +1410,7 @@ wymix = \a, b ->
wymum : U64, U64 -> { lower : U64, upper : U64 }
wymum = \a, b ->
r = Num.toU128 a * Num.toU128 b
r = Num.mulWrap (Num.toU128 a) (Num.toU128 b)
lower = Num.toU64 r
upper = Num.shiftRightZfBy r 64 |> Num.toU64
@ -1426,7 +1419,7 @@ wymum = \a, b ->
{ lower, upper }
# Get the next 8 bytes as a U64
wyr8 : List U8, Nat -> U64
wyr8 : List U8, U64 -> U64
wyr8 = \list, index ->
# With seamless slices and Num.fromBytes, this should be possible to make faster and nicer.
# It would also deal with the fact that on big endian systems we want to invert the order here.
@ -1447,7 +1440,7 @@ wyr8 = \list, index ->
Num.bitwiseOr (Num.bitwiseOr a b) (Num.bitwiseOr c d)
# Get the next 4 bytes as a U64 with some shifting.
wyr4 : List U8, Nat -> U64
wyr4 : List U8, U64 -> U64
wyr4 = \list, index ->
p1 = listGetUnsafe list index |> Num.toU64
p2 = listGetUnsafe list (Num.addWrap index 1) |> Num.toU64
@ -1460,7 +1453,7 @@ wyr4 = \list, index ->
# Get the next K bytes with some shifting.
# K must be 3 or less.
wyr3 : List U8, Nat, Nat -> U64
wyr3 : List U8, U64, U64 -> U64
wyr3 = \list, index, k ->
# ((uint64_t)p[0])<<16)|(((uint64_t)p[k>>1])<<8)|p[k-1]
p1 = listGetUnsafe list index |> Num.toU64

View file

@ -73,14 +73,46 @@ EncoderFormatting implements
tuple : List (Encoder fmt) -> Encoder fmt where fmt implements EncoderFormatting
tag : Str, List (Encoder fmt) -> Encoder fmt where fmt implements EncoderFormatting
## Creates a custom encoder from a given function.
##
## ```
## expect
## # Appends the byte 42
## customEncoder = Encode.custom (\bytes, _fmt -> List.append bytes 42)
##
## actual = Encode.appendWith [] customEncoder Core.json
## expected = [42] # Expected result is a list with a single byte, 42
##
## actual == expected
## ```
custom : (List U8, fmt -> List U8) -> Encoder fmt where fmt implements EncoderFormatting
custom = \encoder -> @Encoder encoder
appendWith : List U8, Encoder fmt, fmt -> List U8 where fmt implements EncoderFormatting
appendWith = \lst, @Encoder doEncoding, fmt -> doEncoding lst fmt
## Appends the encoded representation of a value to an existing list of bytes.
##
## ```
## expect
## actual = Encode.append [] { foo: 43 } Core.json
## expected = Str.toUtf8 """{"foo":43}"""
##
## actual == expected
## ```
append : List U8, val, fmt -> List U8 where val implements Encoding, fmt implements EncoderFormatting
append = \lst, val, fmt -> appendWith lst (toEncoder val) fmt
## Encodes a value to a list of bytes (`List U8`) according to the specified format.
##
## ```
## expect
## fooRec = { foo: 42 }
##
## actual = Encode.toBytes fooRec Core.json
## expected = Str.toUtf8 """{"foo":42}"""
##
## actual == expected
## ```
toBytes : val, fmt -> List U8 where val implements Encoding, fmt implements EncoderFormatting
toBytes = \val, fmt -> appendWith [] (toEncoder val) fmt

View file

@ -15,17 +15,16 @@ interface Hash
hashI32,
hashI64,
hashI128,
hashNat,
hashDec,
complete,
hashStrBytes,
hashList,
hashUnordered,
] imports [
Bool.{ Bool, isEq },
Bool.{ Bool },
List,
Str,
Num.{ U8, U16, U32, U64, U128, I8, I16, I32, I64, I128, Nat, Dec },
Num.{ U8, U16, U32, U64, U128, I8, I16, I32, I64, I128, Dec },
]
## A value that can be hashed.
@ -98,21 +97,6 @@ hashI64 = \hasher, n -> addU64 hasher (Num.toU64 n)
hashI128 : a, I128 -> a where a implements Hasher
hashI128 = \hasher, n -> addU128 hasher (Num.toU128 n)
## Adds a single Nat to a hasher.
hashNat : a, Nat -> a where a implements Hasher
hashNat = \hasher, n ->
isPlatform32bit =
x : Nat
x = 0xffff_ffff
y = Num.addWrap x 1
y == 0
if isPlatform32bit then
addU32 hasher (Num.toU32 n)
else
addU64 hasher (Num.toU64 n)
## LOWLEVEL get the i128 representation of a Dec.
i128OfDec : Dec -> I128

View file

@ -27,7 +27,6 @@ interface Inspect
i64,
u128,
i128,
nat,
f32,
f64,
dec,
@ -38,7 +37,7 @@ interface Inspect
]
imports [
Bool.{ Bool },
Num.{ U8, U16, U32, U64, U128, I8, I16, I32, I64, I128, F32, F64, Dec, Nat },
Num.{ U8, U16, U32, U64, U128, I8, I16, I32, I64, I128, F32, F64, Dec },
List,
Str,
]
@ -77,7 +76,6 @@ InspectFormatter implements
i64 : I64 -> Inspector f where f implements InspectFormatter
u128 : U128 -> Inspector f where f implements InspectFormatter
i128 : I128 -> Inspector f where f implements InspectFormatter
nat : Nat -> Inspector f where f implements InspectFormatter
f32 : F32 -> Inspector f where f implements InspectFormatter
f64 : F64 -> Inspector f where f implements InspectFormatter
dec : Dec -> Inspector f where f implements InspectFormatter
@ -131,7 +129,6 @@ DbgFormatter := { data : Str }
i64: dbgI64,
u128: dbgU128,
i128: dbgI128,
nat: dbgNat,
f32: dbgF32,
f64: dbgF64,
dec: dbgDec,
@ -326,11 +323,6 @@ dbgI128 = \num ->
f0 <- custom
dbgWrite f0 (num |> Num.toStr)
dbgNat : Nat -> Inspector DbgFormatter
dbgNat = \num ->
f0 <- custom
dbgWrite f0 (num |> Num.toStr)
dbgF32 : F32 -> Inspector DbgFormatter
dbgF32 = \num ->
f0 <- custom

View file

@ -74,7 +74,7 @@ interface List
imports [
Bool.{ Bool, Eq },
Result.{ Result },
Num.{ Nat, Num, Int },
Num.{ U64, Num, Int },
]
## ## Types
@ -91,14 +91,14 @@ interface List
## is normally enabled, not having enough memory could result in the list appearing
## to be created just fine, but then crashing later.)
##
## > The theoretical maximum length for a list created in Roc is half of
## > `Num.maxNat`. Attempting to create a list bigger than that
## > The theoretical maximum length for a list created in Roc is `Num.maxI32` on 32-bit systems
## > and `Num.maxI64` on 64-bit systems. Attempting to create a list bigger than that
## > in Roc code will always fail, although in practice it is likely to fail
## > at much smaller lengths due to insufficient memory being available.
##
## ## Performance Details
##
## Under the hood, a list is a record containing a `len : Nat` field, a `capacity : Nat`
## Under the hood, a list is a record containing a `len : U64` field, a `capacity : U64`
## field, and a pointer to a reference count and a flat array of bytes.
##
## ## Shared Lists
@ -227,7 +227,7 @@ isEmpty = \list ->
# unsafe primitive that does not perform a bounds check
# but will cause a reference count increment on the value it got out of the list
getUnsafe : List a, Nat -> a
getUnsafe : List a, U64 -> a
## Returns an element from a list at the given index.
##
@ -236,7 +236,7 @@ getUnsafe : List a, Nat -> a
## expect List.get [100, 200, 300] 1 == Ok 200
## expect List.get [100, 200, 300] 5 == Err OutOfBounds
## ```
get : List a, Nat -> Result a [OutOfBounds]
get : List a, U64 -> Result a [OutOfBounds]
get = \list, index ->
if index < List.len list then
Ok (List.getUnsafe list index)
@ -245,9 +245,9 @@ get = \list, index ->
# unsafe primitive that does not perform a bounds check
# but will cause a reference count increment on the value it got out of the list
replaceUnsafe : List a, Nat, a -> { list : List a, value : a }
replaceUnsafe : List a, U64, a -> { list : List a, value : a }
replace : List a, Nat, a -> { list : List a, value : a }
replace : List a, U64, a -> { list : List a, value : a }
replace = \list, index, newValue ->
if index < List.len list then
List.replaceUnsafe list index newValue
@ -262,7 +262,7 @@ replace = \list, index, newValue ->
## list unmodified.
##
## To drop the element at a given index, instead of replacing it, see [List.dropAt].
set : List a, Nat, a -> List a
set : List a, U64, a -> List a
set = \list, index, value ->
(List.replace list index value).list
@ -275,7 +275,7 @@ set = \list, index, value ->
##
## To replace the element at a given index, instead of updating based on the current value,
## see [List.set] and [List.replace]
update : List a, Nat, (a -> a) -> List a
update : List a, U64, (a -> a) -> List a
update = \list, index, func ->
when List.get list index is
Err OutOfBounds -> list
@ -285,7 +285,7 @@ update = \list, index, func ->
# Update one element in bounds
expect
list : List Nat
list : List U64
list = [1, 2, 3]
got = update list 1 (\x -> x + 42)
want = [1, 44, 3]
@ -293,14 +293,14 @@ expect
# Update out of bounds
expect
list : List Nat
list : List U64
list = [1, 2, 3]
got = update list 5 (\x -> x + 42)
got == list
# Update chain
expect
list : List Nat
list : List U64
list = [1, 2, 3]
got =
list
@ -374,13 +374,13 @@ prependIfOk = \list, result ->
## One [List] can store up to 2,147,483,648 elements (just over 2 billion), which
## is exactly equal to the highest valid #I32 value. This means the #U32 this function
## returns can always be safely converted to an #I32 without losing any data.
len : List * -> Nat
len : List * -> U64
## Create a list with space for at least capacity elements
withCapacity : Nat -> List *
withCapacity : U64 -> List *
## Enlarge the list for at least capacity additional elements
reserve : List a, Nat -> List a
reserve : List a, U64 -> List a
## Shrink the memory footprint of a list such that it's capacity and length are equal.
## Note: This will also convert seamless slices to regular lists.
@ -418,11 +418,11 @@ single : a -> List a
single = \x -> [x]
## Returns a list with the given length, where every element is the given value.
repeat : a, Nat -> List a
repeat : a, U64 -> List a
repeat = \value, count ->
repeatHelp value count (List.withCapacity count)
repeatHelp : a, Nat, List a -> List a
repeatHelp : a, U64, List a -> List a
repeatHelp = \value, count, accum ->
if count > 0 then
repeatHelp value (Num.subWrap count 1) (List.appendUnsafe accum value)
@ -435,7 +435,8 @@ repeatHelp = \value, count, accum ->
## ```
reverse : List a -> List a
reverse = \list ->
reverseHelp list 0 (Num.subSaturated (List.len list) 1)
end = List.len list |> Num.subSaturated 1
reverseHelp (List.clone list) 0 end
reverseHelp = \list, left, right ->
if left < right then
@ -443,6 +444,9 @@ reverseHelp = \list, left, right ->
else
list
# Ensures that the list in unique (will re-use if already unique)
clone : List a -> List a
## Join the given lists together into one list.
## ```
## expect List.join [[1], [2, 3], [], [4, 5]] == [1, 2, 3, 4, 5]
@ -497,7 +501,7 @@ walk = \list, init, func ->
walkHelp list init func 0 (List.len list)
## internal helper
walkHelp : List elem, s, (s, elem -> s), Nat, Nat -> s
walkHelp : List elem, s, (s, elem -> s), U64, U64 -> s
walkHelp = \list, state, f, index, length ->
if index < length then
nextState = f state (List.getUnsafe list index)
@ -507,12 +511,12 @@ walkHelp = \list, state, f, index, length ->
state
## Like [walk], but at each step the function also receives the index of the current element.
walkWithIndex : List elem, state, (state, elem, Nat -> state) -> state
walkWithIndex : List elem, state, (state, elem, U64 -> state) -> state
walkWithIndex = \list, init, func ->
walkWithIndexHelp list init func 0 (List.len list)
## internal helper
walkWithIndexHelp : List elem, s, (s, elem, Nat -> s), Nat, Nat -> s
walkWithIndexHelp : List elem, s, (s, elem, U64 -> s), U64, U64 -> s
walkWithIndexHelp = \list, state, f, index, length ->
if index < length then
nextState = f state (List.getUnsafe list index) index
@ -522,14 +526,14 @@ walkWithIndexHelp = \list, state, f, index, length ->
state
## Like [walkUntil], but at each step the function also receives the index of the current element.
walkWithIndexUntil : List elem, state, (state, elem, Nat -> [Continue state, Break state]) -> state
walkWithIndexUntil : List elem, state, (state, elem, U64 -> [Continue state, Break state]) -> state
walkWithIndexUntil = \list, state, f ->
when walkWithIndexUntilHelp list state f 0 (List.len list) is
Continue new -> new
Break new -> new
## internal helper
walkWithIndexUntilHelp : List elem, s, (s, elem, Nat -> [Continue s, Break b]), Nat, Nat -> [Continue s, Break b]
walkWithIndexUntilHelp : List elem, s, (s, elem, U64 -> [Continue s, Break b]), U64, U64 -> [Continue s, Break b]
walkWithIndexUntilHelp = \list, state, f, index, length ->
if index < length then
when f state (List.getUnsafe list index) index is
@ -547,7 +551,7 @@ walkBackwards = \list, state, func ->
walkBackwardsHelp list state func (len list)
## internal helper
walkBackwardsHelp : List elem, state, (state, elem -> state), Nat -> state
walkBackwardsHelp : List elem, state, (state, elem -> state), U64 -> state
walkBackwardsHelp = \list, state, f, indexPlusOne ->
if indexPlusOne == 0 then
state
@ -582,7 +586,7 @@ walkBackwardsUntil = \list, initial, func ->
Break new -> new
## Walks to the end of the list from a specified starting index
walkFrom : List elem, Nat, state, (state, elem -> state) -> state
walkFrom : List elem, U64, state, (state, elem -> state) -> state
walkFrom = \list, index, state, func ->
step : _, _ -> [Continue _, Break []]
step = \currentState, element -> Continue (func currentState element)
@ -591,7 +595,7 @@ walkFrom = \list, index, state, func ->
Continue new -> new
## A combination of [List.walkFrom] and [List.walkUntil]
walkFromUntil : List elem, Nat, state, (state, elem -> [Continue state, Break state]) -> state
walkFromUntil : List elem, U64, state, (state, elem -> [Continue state, Break state]) -> state
walkFromUntil = \list, index, state, func ->
when List.iterHelp list state func index (List.len list) is
Continue new -> new
@ -660,7 +664,7 @@ keepIf = \list, predicate ->
keepIfHelp list predicate 0 0 length
keepIfHelp : List a, (a -> Bool), Nat, Nat, Nat -> List a
keepIfHelp : List a, (a -> Bool), U64, U64, U64 -> List a
keepIfHelp = \list, predicate, kept, index, length ->
if index < length then
if predicate (List.getUnsafe list index) then
@ -689,7 +693,7 @@ dropIf = \list, predicate ->
## expect List.countIf [1, -2, -3] Num.isNegative == 2
## expect List.countIf [1, 2, 3] (\num -> num > 1 ) == 2
## ```
countIf : List a, (a -> Bool) -> Nat
countIf : List a, (a -> Bool) -> U64
countIf = \list, predicate ->
walkState = \state, elem ->
if predicate elem then
@ -771,7 +775,7 @@ map4 : List a, List b, List c, List d, (a, b, c, d -> e) -> List e
## ```
## expect List.mapWithIndex [10, 20, 30] (\num, index -> num + index) == [10, 21, 32]
## ```
mapWithIndex : List a, (a, Nat -> b) -> List b
mapWithIndex : List a, (a, U64 -> b) -> List b
mapWithIndex = \src, func ->
length = len src
dest = withCapacity length
@ -779,7 +783,7 @@ mapWithIndex = \src, func ->
mapWithIndexHelp src dest func 0 length
# Internal helper
mapWithIndexHelp : List a, List b, (a, Nat -> b), Nat, Nat -> List b
mapWithIndexHelp : List a, List b, (a, U64 -> b), U64, U64 -> List b
mapWithIndexHelp = \src, dest, func, index, length ->
if index < length then
elem = getUnsafe src index
@ -954,7 +958,7 @@ sortAsc = \list -> List.sortWith list Num.compare
sortDesc : List (Num a) -> List (Num a)
sortDesc = \list -> List.sortWith list (\a, b -> Num.compare b a)
swap : List a, Nat, Nat -> List a
swap : List a, U64, U64 -> List a
## Returns the first element in the list, or `ListWasEmpty` if it was empty.
first : List a -> Result a [ListWasEmpty]
@ -979,7 +983,7 @@ first = \list ->
##
## To split the list into two lists, use `List.split`.
##
takeFirst : List elem, Nat -> List elem
takeFirst : List elem, U64 -> List elem
takeFirst = \list, outputLength ->
List.sublist list { start: 0, len: outputLength }
@ -999,19 +1003,19 @@ takeFirst = \list, outputLength ->
##
## To split the list into two lists, use `List.split`.
##
takeLast : List elem, Nat -> List elem
takeLast : List elem, U64 -> List elem
takeLast = \list, outputLength ->
List.sublist list { start: Num.subSaturated (List.len list) outputLength, len: outputLength }
## Drops n elements from the beginning of the list.
dropFirst : List elem, Nat -> List elem
dropFirst : List elem, U64 -> List elem
dropFirst = \list, n ->
remaining = Num.subSaturated (List.len list) n
List.takeLast list remaining
## Drops n elements from the end of the list.
dropLast : List elem, Nat -> List elem
dropLast : List elem, U64 -> List elem
dropLast = \list, n ->
remaining = Num.subSaturated (List.len list) n
@ -1022,7 +1026,7 @@ dropLast = \list, n ->
## This has no effect if the given index is outside the bounds of the list.
##
## To replace the element at a given index, instead of dropping it, see [List.set].
dropAt : List elem, Nat -> List elem
dropAt : List elem, U64 -> List elem
min : List (Num a) -> Result (Num a) [ListWasEmpty]
min = \list ->
@ -1097,7 +1101,7 @@ findLast = \list, pred ->
## Returns the index at which the first element in the list
## satisfying a predicate function can be found.
## If no satisfying element is found, an `Err NotFound` is returned.
findFirstIndex : List elem, (elem -> Bool) -> Result Nat [NotFound]
findFirstIndex : List elem, (elem -> Bool) -> Result U64 [NotFound]
findFirstIndex = \list, matcher ->
foundIndex = List.iterate list 0 \index, elem ->
if matcher elem then
@ -1112,7 +1116,7 @@ findFirstIndex = \list, matcher ->
## Returns the last index at which the first element in the list
## satisfying a predicate function can be found.
## If no satisfying element is found, an `Err NotFound` is returned.
findLastIndex : List elem, (elem -> Bool) -> Result Nat [NotFound]
findLastIndex : List elem, (elem -> Bool) -> Result U64 [NotFound]
findLastIndex = \list, matches ->
foundIndex = List.iterateBackwards list (List.len list) \prevIndex, elem ->
answer = Num.subWrap prevIndex 1
@ -1141,12 +1145,12 @@ findLastIndex = \list, matches ->
## > matter how long the list is, `List.takeLast` can do that more efficiently.
##
## Some languages have a function called **`slice`** which works similarly to this.
sublist : List elem, { start : Nat, len : Nat } -> List elem
sublist : List elem, { start : U64, len : U64 } -> List elem
sublist = \list, config ->
sublistLowlevel list config.start config.len
## low-level slicing operation that does no bounds checking
sublistLowlevel : List elem, Nat, Nat -> List elem
sublistLowlevel : List elem, U64, U64 -> List elem
## Intersperses `sep` between the elements of `list`
## ```
@ -1198,7 +1202,7 @@ endsWith = \list, suffix ->
## than the given index, # and the `others` list will be all the others. (This
## means if you give an index of 0, the `before` list will be empty and the
## `others` list will have the same elements as the original list.)
split : List elem, Nat -> { before : List elem, others : List elem }
split : List elem, U64 -> { before : List elem, others : List elem }
split = \elements, userSplitIndex ->
length = List.len elements
splitIndex = if length > userSplitIndex then userSplitIndex else length
@ -1243,7 +1247,7 @@ splitLast = \list, delimiter ->
## size. The last chunk will be shorter if the list does not evenly divide by the
## chunk size. If the provided list is empty or if the chunk size is 0 then the
## result is an empty list.
chunksOf : List a, Nat -> List (List a)
chunksOf : List a, U64 -> List (List a)
chunksOf = \list, chunkSize ->
if chunkSize == 0 || List.isEmpty list then
[]
@ -1251,7 +1255,7 @@ chunksOf = \list, chunkSize ->
chunkCapacity = Num.divCeil (List.len list) chunkSize
chunksOfHelp list chunkSize (List.withCapacity chunkCapacity)
chunksOfHelp : List a, Nat, List (List a) -> List (List a)
chunksOfHelp : List a, U64, List (List a) -> List (List a)
chunksOfHelp = \listRest, chunkSize, chunks ->
if List.isEmpty listRest then
chunks
@ -1284,7 +1288,7 @@ walkTry = \list, init, func ->
walkTryHelp list init func 0 (List.len list)
## internal helper
walkTryHelp : List elem, state, (state, elem -> Result state err), Nat, Nat -> Result state err
walkTryHelp : List elem, state, (state, elem -> Result state err), U64, U64 -> Result state err
walkTryHelp = \list, state, f, index, length ->
if index < length then
when f state (List.getUnsafe list index) is
@ -1299,7 +1303,7 @@ iterate = \list, init, func ->
iterHelp list init func 0 (List.len list)
## internal helper
iterHelp : List elem, s, (s, elem -> [Continue s, Break b]), Nat, Nat -> [Continue s, Break b]
iterHelp : List elem, s, (s, elem -> [Continue s, Break b]), U64, U64 -> [Continue s, Break b]
iterHelp = \list, state, f, index, length ->
if index < length then
when f state (List.getUnsafe list index) is
@ -1315,7 +1319,7 @@ iterateBackwards = \list, init, func ->
iterBackwardsHelp list init func (List.len list)
## internal helper
iterBackwardsHelp : List elem, s, (s, elem -> [Continue s, Break b]), Nat -> [Continue s, Break b]
iterBackwardsHelp : List elem, s, (s, elem -> [Continue s, Break b]), U64 -> [Continue s, Break b]
iterBackwardsHelp = \list, state, f, prevIndex ->
if prevIndex > 0 then
index = Num.subWrap prevIndex 1

View file

@ -25,11 +25,9 @@ interface Num
Unsigned32,
Unsigned16,
Unsigned8,
Nat,
Dec,
F64,
F32,
Natural,
Decimal,
Binary32,
Binary64,
@ -48,6 +46,7 @@ interface Num
isLte,
isGt,
isGte,
isApproxEq,
sin,
cos,
tan,
@ -97,10 +96,6 @@ interface Num
mulSaturated,
mulChecked,
intCast,
bytesToU16,
bytesToU32,
bytesToU64,
bytesToU128,
divCeil,
divCeilChecked,
divTrunc,
@ -151,8 +146,6 @@ interface Num
toU64Checked,
toU128,
toU128Checked,
toNat,
toNatChecked,
toF32,
toF32Checked,
toF64,
@ -192,9 +185,9 @@ interface Num
## a more specific type based on how they're used.
##
## For example, in `(1 + List.len myList)`, the `1` has the type `Num *` at first,
## but because `List.len` returns a `Nat`, the `1` ends up changing from
## `Num *` to the more specific `Nat`, and the expression as a whole
## ends up having the type `Nat`.
## but because `List.len` returns a `U64`, the `1` ends up changing from
## `Num *` to the more specific `U64`, and the expression as a whole
## ends up having the type `U64`.
##
## Sometimes number literals don't become more specific. For example,
## the `Num.toStr` function has the type `Num * -> Str`. This means that
@ -216,7 +209,6 @@ interface Num
## * `215u8` is a `215` value of type [U8]
## * `76.4f32` is a `76.4` value of type [F32]
## * `123.45dec` is a `123.45` value of type [Dec]
## * `12345nat` is a `12345` value of type [Nat]
##
## In practice, these are rarely needed. It's most common to write
## number literals without any suffix.
@ -326,16 +318,6 @@ Num range := range
## | ` (over 340 undecillion) 0` | [U128]| 16 Bytes |
## | ` 340_282_366_920_938_463_463_374_607_431_768_211_455` | | |
##
## Roc also has one variable-size integer type: [Nat]. The size of [Nat] is equal
## to the size of a memory address, which varies by system. For example, when
## compiling for a 64-bit system, [Nat] is the same as [U64]. When compiling for a
## 32-bit system, it's the same as [U32].
##
## A common use for [Nat] is to store the length ("len" for short) of a
## collection like a [List]. 64-bit systems can represent longer
## lists in memory than 32-bit systems, 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
## or too small to fit in that range (e.g. calling `Num.maxI32 + 1`),
## then the operation will *overflow*. When an overflow occurs, the program will crash.
@ -425,8 +407,6 @@ Unsigned32 := []
Unsigned16 := []
Unsigned8 := []
Natural := []
Integer range := range
I128 : Num (Integer Signed128)
@ -443,18 +423,6 @@ U32 : Num (Integer Unsigned32)
U16 : Num (Integer Unsigned16)
U8 : Num (Integer Unsigned8)
## A [natural number](https://en.wikipedia.org/wiki/Natural_number) represented
## as a 64-bit unsigned integer on 64-bit systems, a 32-bit unsigned integer
## on 32-bit systems, and so on.
##
## This system-specific size makes it useful for certain data structure
## functions like [List.len], because the number of elements many data structures
## can hold is also system-specific. For example, the maximum number of elements
## a [List] can hold on a 64-bit system fits in a 64-bit unsigned integer, and
## on a 32-bit system it fits in 32-bit unsigned integer. This makes [Nat] a
## good fit for [List.len] regardless of system.
Nat : Num (Integer Natural)
Decimal := []
Binary64 := []
Binary32 := []
@ -574,51 +542,6 @@ tau = 2 * pi
toStr : Num * -> Str
intCast : Int a -> Int b
bytesToU16Lowlevel : List U8, Nat -> U16
bytesToU32Lowlevel : List U8, Nat -> U32
bytesToU64Lowlevel : List U8, Nat -> U64
bytesToU128Lowlevel : List U8, Nat -> U128
bytesToU16 : List U8, Nat -> Result U16 [OutOfBounds]
bytesToU16 = \bytes, index ->
# we need at least 1 more byte
offset = 1
if Num.addSaturated index offset < List.len bytes then
Ok (bytesToU16Lowlevel bytes index)
else
Err OutOfBounds
bytesToU32 : List U8, Nat -> Result U32 [OutOfBounds]
bytesToU32 = \bytes, index ->
# we need at least 3 more bytes
offset = 3
if Num.addSaturated index offset < List.len bytes then
Ok (bytesToU32Lowlevel bytes index)
else
Err OutOfBounds
bytesToU64 : List U8, Nat -> Result U64 [OutOfBounds]
bytesToU64 = \bytes, index ->
# we need at least 7 more bytes
offset = 7
if Num.addSaturated index offset < List.len bytes then
Ok (bytesToU64Lowlevel bytes index)
else
Err OutOfBounds
bytesToU128 : List U8, Nat -> Result U128 [OutOfBounds]
bytesToU128 = \bytes, index ->
# we need at least 15 more bytes
offset = 15
if Num.addSaturated index offset < List.len bytes then
Ok (bytesToU128Lowlevel bytes index)
else
Err OutOfBounds
compare : Num a, Num a -> [LT, EQ, GT]
## Returns `Bool.true` if the first number is less than the second.
@ -661,6 +584,22 @@ isLte : Num a, Num a -> Bool
## is [defined to be unordered](https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN).)
isGte : Num a, Num a -> Bool
## Returns `Bool.true` if the first number and second number are within a specific threshold
##
## A specific relative and absolute tolerance can be provided to change the threshold
##
## If either argument is [*NaN*](Num.isNaN), returns `Bool.false` no matter what. (*NaN*
## is [defined to be unordered](https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN).)
isApproxEq : Frac a, Frac a, { rtol ? Frac a, atol ? Frac a } -> Bool
isApproxEq = \value, refValue, { rtol ? 0.00001, atol ? 0.00000001 } -> value
<= refValue
&& value
>= refValue
|| Num.absDiff value refValue
<= atol
+ rtol
* Num.abs refValue
## Returns `Bool.true` if the number is `0`, and `Bool.false` otherwise.
isZero : Num a -> Bool
@ -985,13 +924,21 @@ divCeilChecked = \a, b ->
## Num.divTrunc 8 -3
## ```
divTrunc : Int a, Int a -> Int a
divTrunc = \a, b ->
if Num.isZero b then
crash "Integer division by 0!"
else
Num.divTruncUnchecked a b
divTruncChecked : Int a, Int a -> Result (Int a) [DivByZero]
divTruncChecked = \a, b ->
if Num.isZero b then
Err DivByZero
else
Ok (Num.divTrunc a b)
Ok (Num.divTruncUnchecked a b)
## traps (hardware fault) when given zero as the second argument.
divTruncUnchecked : Int a, Int a -> Int a
## Obtains the remainder (truncating modulo) from the division of two integers.
##
@ -1006,13 +953,21 @@ divTruncChecked = \a, b ->
## Num.rem -8 -3
## ```
rem : Int a, Int a -> Int a
rem = \a, b ->
if Num.isZero b then
crash "Integer division by 0!"
else
Num.remUnchecked a b
remChecked : Int a, Int a -> Result (Int a) [DivByZero]
remChecked = \a, b ->
if Num.isZero b then
Err DivByZero
else
Ok (Num.rem a b)
Ok (Num.remUnchecked a b)
## traps (hardware fault) when given zero as the second argument.
remUnchecked : Int a, Int a -> Int a
isMultipleOf : Int a, Int a -> Bool
@ -1430,22 +1385,6 @@ toU32 : Int * -> U32
toU64 : Int * -> U64
toU128 : Int * -> U128
## Converts an [Int] to a [Nat]. If the given number doesn't fit in [Nat], it will be truncated!
## Since [Nat] has a different maximum number depending on the system you're building
## for, this may give a different answer on different systems.
##
## For example, on a 32-bit system, calling `Num.toNat 9_000_000_000` on a 32-bit
## system will return `Num.maxU32` instead of 9 billion, because 9 billion is
## higher than `Num.maxU32` and will not fit in a [Nat] on a 32-bit system.
##
## However, calling `Num.toNat 9_000_000_000` on a 64-bit system will return
## the [Nat] value of 9_000_000_000. This is because on a 64-bit system, [Nat] can
## hold up to `Num.maxU64`, and 9_000_000_000 is lower than `Num.maxU64`.
##
## To convert a [Frac] to a [Nat], first call either `Num.round`, `Num.ceil`, or `Num.floor`
## on it, then call this on the resulting [Int].
toNat : Int * -> Nat
## Converts a [Num] to an [F32]. If the given number can't be precisely represented in an [F32],
## the returned number may be different from the given number.
toF32 : Num * -> F32
@ -1467,6 +1406,5 @@ toU16Checked : Int * -> Result U16 [OutOfBounds]
toU32Checked : Int * -> Result U32 [OutOfBounds]
toU64Checked : Int * -> Result U64 [OutOfBounds]
toU128Checked : Int * -> Result U128 [OutOfBounds]
toNatChecked : Int * -> Result Nat [OutOfBounds]
toF32Checked : Num * -> Result F32 [OutOfBounds]
toF64Checked : Num * -> Result F64 [OutOfBounds]

View file

@ -84,8 +84,8 @@ try = \result, transform ->
## function on the value the `Err` holds. Then returns that new result. If the
## result is `Ok`, this has no effect. Use `try` to transform an `Ok`.
## ```
## Result.onErr (Ok 10) \errorNum -> Str.toNat errorNum
## Result.onErr (Err "42") \errorNum -> Str.toNat errorNum
## Result.onErr (Ok 10) \errorNum -> Str.toU64 errorNum
## Result.onErr (Err "42") \errorNum -> Str.toU64 errorNum
## ```
onErr : Result a err, (err -> Result a otherErr) -> Result a otherErr
onErr = \result, transform ->

View file

@ -28,7 +28,7 @@ interface Set
List,
Bool.{ Bool, Eq },
Dict.{ Dict },
Num.{ Nat },
Num.{ U64 },
Hash.{ Hash, Hasher },
Inspect.{ Inspect, Inspector, InspectFormatter },
]
@ -80,12 +80,12 @@ empty = \{} -> @Set (Dict.empty {})
## Return a set with space allocated for a number of entries. This
## may provide a performance optimization if you know how many entries will be
## inserted.
withCapacity : Nat -> Set *
withCapacity : U64 -> Set *
withCapacity = \cap ->
@Set (Dict.withCapacity cap)
## Enlarge the set for at least capacity additional elements
reserve : Set k, Nat -> Set k
reserve : Set k, U64 -> Set k
reserve = \@Set dict, requested ->
@Set (Dict.reserve dict requested)
@ -152,7 +152,7 @@ expect
##
## expect countValues == 3
## ```
len : Set * -> Nat
len : Set * -> U64
len = \@Set dict ->
Dict.len dict
@ -164,7 +164,7 @@ len = \@Set dict ->
##
## capacityOfSet = Set.capacity foodSet
## ```
capacity : Set * -> Nat
capacity : Set * -> U64
capacity = \@Set dict ->
Dict.capacity dict
@ -462,22 +462,22 @@ expect
x == fromList (toList x)
expect
orderOne : Set Nat
orderOne : Set U64
orderOne =
single 1
|> insert 2
orderTwo : Set Nat
orderTwo : Set U64
orderTwo =
single 2
|> insert 1
wrapperOne : Set (Set Nat)
wrapperOne : Set (Set U64)
wrapperOne =
single orderOne
|> insert orderTwo
wrapperTwo : Set (Set Nat)
wrapperTwo : Set (Set U64)
wrapperTwo =
single orderTwo
|> insert orderOne

View file

@ -1,90 +1,331 @@
## Roc strings are sequences of text values. This module includes functions for combining strings,
## as well as breaking them up into smaller units—most commonly [extended grapheme clusters](http://www.unicode.org/glossary/#extended_grapheme_cluster)
## (referred to in this module's documentation as "graphemes" rather than "characters" for clarity;
## "characters" can mean very different things in different languages).
## Strings represent text. For example, `"Hi!"` is a string.
##
## This module focuses on graphemes (as opposed to, say, Unicode code points or LATIN-1 bytes)
## because graphemes avoid common classes of bugs. Breaking strings up using code points often
## leads to bugs around things like emoji, where multiple code points combine to form to a
## single rendered glyph. Graphemes avoid these bugs by treating multi-code-point things like
## emojis as indivisible units.
## This guide starts at a high level and works down to the in-memory representation of strings and their [performance characteristics](#performance). For reasons that will be explained later in this guide, some string operations are in the `Str` module while others (notably [capitalization](#capitalization), [code points](#code-points), [graphemes](#graphemes), and sorting) are in separate packages. There's also a list of recommendations for [when to use code points, graphemes, and UTF-8](#when-to-use).
##
## Because graphemes can have variable length (there's no upper limit on how many code points one
## grapheme can represent), it takes linear time to count the number of graphemes in a string,
## and also linear time to find an individual grapheme within a string by its position (or "index")
## among the string's other graphemes. The only way to get constant-time access to these is in a way
## that can result in bugs if the string contains multi-code-point things like emojis, which is why
## this module does not offer those.
## ## Syntax
##
## The most common way to represent strings is using quotation marks:
##
## ## Working with Unicode strings in Roc
##
## Unicode can represent text values which span multiple languages, symbols, and emoji.
## Here are some valid Roc strings:
## ```
## "Roc!"
## "鹏"
## "🕊"
## "Hello, World!"
## ```
## Every Unicode string is a sequence of [extended grapheme clusters](http://www.unicode.org/glossary/#extended_grapheme_cluster).
## An extended grapheme cluster represents what a person reading a string might
## call a "character" - like "A" or "ö" or "👩‍👩‍👦‍👦".
## Because the term "character" means different things in different areas of
## programming, and "extended grapheme cluster" is a mouthful, in Roc we use the
## term "grapheme" as a shorthand for the more precise "extended grapheme cluster."
##
## You can get the number of graphemes in a string by calling `Str.countGraphemes` on it:
## Using this syntax, the whole string must go on one line. You can write multiline strings using triple quotes:
##
## ```
## Str.countGraphemes "Roc!"
## Str.countGraphemes "折り紙"
## Str.countGraphemes "🕊"
## text =
## """
## In memory, this string will not have any spaces
## at its start. That's because the first line
## starts at the same indentation level as the
## opening quotation mark. Actually, none of these
## lines will be indented.
##
## However, this line will be indented!
## """
## ```
## > The `countGraphemes` function walks through the entire string to get its answer,
## > so if you want to check whether a string is empty, you'll get much better performance
## > by calling `Str.isEmpty myStr` instead of `Str.countGraphemes myStr == 0`.
##
## ### Escape sequences
## In triple-quoted strings, both the opening and closing `"""` must be at the same indentation level. Lines in the string begin at that indentation level; the spaces that indent the multiline string itself are not considered content.
##
## ### Interpolation
##
## *String interpolation* is syntax for inserting a string into another string.
##
## If you put a `\` in a Roc string literal, it begins an *escape sequence*.
## An escape sequence is a convenient way to insert certain strings into other strings.
## For example, suppose you write this Roc string:
## ```
## "I took the one less traveled by,\nAnd that has made all the difference."
## name = "Sam"
##
## "Hi, my name is $(name)!"
## ```
## The `"\n"` in the middle will insert a line break into this string. There are
## other ways of getting a line break in there, but `"\n"` is the most common.
##
## Another way you could insert a newlines is by writing `\u(0A)` instead of `\n`.
## That would result in the same string, because the `\u` escape sequence inserts
## [Unicode code points](https://unicode.org/glossary/#code_point) directly into
## the string. The Unicode code point 10 is a newline, and 10 is `0A` in hexadecimal.
## `\u` escape sequences are always followed by a hexadecimal number inside `(` and `)`
## like this.
## This will evaluate to the string `"Hi, my name is Sam!"`
##
## As another example, `"R\u(6F)c"` is the same string as `"Roc"`, because
## `"\u(6F)"` corresponds to the Unicode code point for lowercase `o`. If you
## want to [spice things up a bit](https://en.wikipedia.org/wiki/Metal_umlaut),
## you can write `"R\u(F6)c"` as an alternative way to get the string `"Röc"\.
## You can put any expression you like inside the parentheses, as long as it's all on one line:
##
## Roc strings also support these escape sequences:
##
## * `\\` - an actual backslash (writing a single `\` always begins an escape sequence!)
## * `\"` - an actual quotation mark (writing a `"` without a `\` ends the string)
## * `\r` - [carriage return](https://en.wikipedia.org/wiki/Carriage_Return)
## * `\t` - [horizontal tab](https://en.wikipedia.org/wiki/Tab_key#Tab_characters)
## * `\v` - [vertical tab](https://en.wikipedia.org/wiki/Tab_key#Tab_characters)
##
## You can also use escape sequences to insert named strings into other strings, like so:
## ```
## name = "Lee"
## city = "Roctown"
## greeting = "Hello there, \(name)! Welcome to \(city)."
## colors = ["red", "green", "blue"]
##
## "The colors are $(colors |> Str.joinWith ", ")!"
## ```
## Here, `greeting` will become the string `"Hello there, Lee! Welcome to Roctown."`.
## This is known as [string interpolation](https://en.wikipedia.org/wiki/String_interpolation),
## and you can use it as many times as you like inside a string. The name
## between the parentheses must refer to a `Str` value that is currently in
## scope, and it must be a name - it can't be an arbitrary expression like a function call.
##
## Interpolation can be used in multiline strings, but the part inside the parentheses must still be on one line.
##
## ### Escapes
##
## There are a few special escape sequences in strings:
##
## * `\n` becomes a [newline](https://en.wikipedia.org/wiki/Newline)
## * `\r` becomes a [carriage return](https://en.wikipedia.org/wiki/Carriage_return#Computers)
## * `\t` becomes a [tab](https://en.wikipedia.org/wiki/Tab_key#Tab_characters)
## * `\"` becomes a normal `"` (this lets you write `"` inside a single-line string)
## * `\\` becomes a normal `\` (this lets you write `\` without it being treated as an escape)
## * `\$` becomes a normal `$` (this lets you write `$` followed by `(` without it being treated as [interpolation](#interpolation))
##
## These work in both single-line and multiline strings. We'll also discuss another escape later, for inserting [Unicode code points](#code-points) into a string.
##
## ### Single quote syntax
##
## Try putting `'👩'` into `roc repl`. You should see this:
##
## ```
## » '👩'
##
## 128105 : Int *
## ```
##
## The single-quote `'` syntax lets you represent a Unicode code point (discussed in the next section) in source code, in a way that renders as the actual text it represents rather than as a number literal. This lets you see what it looks like in the source code rather than looking at a number.
##
## At runtime, the single-quoted value will be treated the same as an ordinary number literal—in other words, `'👩'` is syntax sugar for writing `128105`. You can verify this in `roc repl`:
##
## ```
## » '👩' == 128105
##
## Bool.true : Bool
## ```
##
## Double quotes (`"`), on the other hand, are not type-compatible with integers—not only because strings can be empty (`""` is valid, but `''` is not) but also because there may be more than one code point involved in any given string!
##
## There are also some special escape sequences in single-quote strings:
##
## * `\n` becomes a [newline](https://en.wikipedia.org/wiki/Newline)
## * `\r` becomes a [carriage return](https://en.wikipedia.org/wiki/Carriage_return#Computers)
## * `\t` becomes a [tab](https://en.wikipedia.org/wiki/Tab_key#Tab_characters)
## * `\'` becomes a normal `'` (this lets you write `'` inside a single-quote string)
## * `\\` becomes a normal `\` (this lets you write `\` without it being treated as an escape)
##
## Most often this single-quote syntax is used when writing parsers; most Roc programs never use it at all.
##
## ## Unicode
##
## Roc strings represent text using [Unicode](https://unicode.org) This guide will provide only a basic overview of Unicode (the [Unicode glossary](http://www.unicode.org/glossary/) has over 500 entries in it), but it will include the most relevant differences between these concepts:
##
## * Code points
## * Graphemes
## * UTF-8
##
## It will also explain why some operations are included in Roc's builtin [Str](https://www.roc-lang.org/builtins/Str)
## module, and why others are in separate packages like [roc-lang/unicode](https://github.com/roc-lang/unicode).
##
## ### Graphemes
##
## Let's start with the following string:
##
## `"👩‍👩‍👦‍👦"`
##
## Some might call this a "character." After all, in a monospace font, it looks to be about the same width as the letter "A" or the punctuation mark "!"—both of which are commonly called "characters." Unfortunately, the term "character" in programming has changed meanings many times across the years and across programming languages, and today it's become a major source of confusion.
##
## Unicode uses the less ambiguous term [*grapheme*](https://www.unicode.org/glossary/#grapheme), which it defines as a "user-perceived character" (as opposed to one of the several historical ways the term "character" has been used in programming) or, alternatively, "A minimally distinctive unit of writing in the context of a particular writing system."
##
## By Unicode's definition, each of the following is an individual grapheme:
##
## * `a`
## * `鹏`
## * `👩‍👩‍👦‍👦`
##
## Note that although *grapheme* is less ambiguous than *character*, its definition is still open to interpretation. To address this, Unicode has formally specified [text segmentation rules](https://www.unicode.org/reports/tr29/) which define grapheme boundaries in precise technical terms. We won't get into those rules here, but since they can change with new Unicode releases, functions for working with graphemes are in the [roc-lang/unicode](https://github.com/roc-lang/unicode) package rather than in the builtin [`Str`](https://www.roc-lang.org/builtins/Str) module. This allows them to be updated without being blocked on a new release of the Roc language.
##
## ### Code Points
##
## Every Unicode text value can be broken down into [Unicode code points](http://www.unicode.org/glossary/#code_point), which are integers between `0` and `285_212_438` that describe components of the text. In memory, every Roc string is a sequence of these integers stored in a format called UTF-8, which will be discussed [later](#utf8).
##
## The string `"👩‍👩‍👦‍👦"` happens to be made up of these code points:
##
## ```
## [128105, 8205, 128105, 8205, 128102, 8205, 128102]
## ```
##
## From this we can see that:
##
## - One grapheme can be made up of multiple code points. In fact, there is no upper limit on how many code points can go into a single grapheme! (Some programming languages use the term "character" to refer to individual code points; this can be confusing for graphemes like 👩‍👩‍👦‍👦 because it visually looks like "one character" but no single code point can represent it.)
## - Sometimes code points repeat within an individual grapheme. Here, 128105 repeats twice, as does 128102, and there's an 8205 in between each of the other code points.
##
## ### Combining Code Points
##
## The reason every other code point in 👩‍👩‍👦‍👦 is 8205 is that code point 8205 joins together other code points. This emoji, known as ["Family: Woman, Woman, Boy, Boy"](https://emojipedia.org/family-woman-woman-boy-boy), is made by combining several emoji using [zero-width joiners](https://emojipedia.org/zero-width-joiner)—which are represented by code point 8205 in memory, and which have no visual repesentation on their own.
##
## Here are those code points again, this time with comments about what they represent:
##
## ```
## [128105] # "👩"
## [8205] # (joiner)
## [128105] # "👩"
## [8205] # (joiner)
## [128102] # "👦"
## [8205] # (joiner)
## [128102] # "👦"
## ```
##
## One way to read this is "woman emoji joined to woman emoji joined to boy emoji joined to boy emoji." Without the joins, it would be:
##
## ```
## "👩👩👦👦"
## ```
##
## With the joins, however, it is instead:
##
## ```
## "👩‍👩‍👦‍👦"
## ```
##
## Even though 👩‍👩‍👦‍👦 is visually smaller when rendered, it takes up almost twice as much memory as 👩👩👦👦 does! That's because it has all the same code points, plus the zero-width joiners in between them.
##
## ### String equality and normalization
##
## Besides emoji like 👩‍👩‍👦‍👦, another classic example of multiple code points being combined to render as one grapheme has to do with accent marks. Try putting these two strings into `roc repl`:
##
## ```
## "caf\u(e9)"
## "cafe\u(301)"
## ```
##
## The `\u(e9)` syntax is a way of inserting code points into string literals. In this case, it's the same as inserting the hexadecimal number `0xe9` as a code point onto the end of the string `"caf"`. Since Unicode code point `0xe9` happens to be `é`, the string `"caf\u(e9)"` ends up being identical in memory to the string `"café"`.
##
## We can verify this too:
##
## ```
## » "caf\u(e9)" == "café"
##
## Bool.true : Bool
## ```
##
## As it turns out, `"cafe\u(301)"` is another way to represent the same word. The Unicode code point 0x301 represents a ["combining acute accent"](https://unicodeplus.com/U+0301)—which essentially means that it will add an accent mark to whatever came before it. In this case, since `"cafe\u(301)"` has an `e` before the `"\u(301)"`, that `e` ends up with an accent mark on it and becomes `é`.
##
## Although these two strings get rendered identically to one another, they are different in memory because their code points are different! We can also confirm this in `roc repl`:
##
## ```
## » "caf\u(e9)" == "cafe\u(301)"
##
## Bool.false : Bool
## ```
##
## As you can imagine, this can be a source of bugs. Not only are they considered unequal, they also hash differently, meaning `"caf\u(e9)"` and `"cafe\u(301)"` can both be separate entries in the same [`Set`](https://www.roc-lang.org/builtins/Set).
##
## One way to prevent problems like these is to perform [Unicode normalization](https://www.unicode.org/reports/tr15/), a process which converts conceptually equivalent strings (like `"caf\u(e9)"` and `"cafe\u(301)"`) into one canonical in-memory representation. This makes equality checks on them pass, among other benefits.
##
## It would be technically possible for Roc to perform string normalization automatically on every equality check. Unfortunately, although some programs might want to treat `"caf\u(e9)"` and `"cafe\u(301)"` as equivalent, for other programs it might actually be important to be able to tell them apart. If these equality checks always passed, then there would be no way to tell them apart!
##
## As such, normalization must be performed explicitly when desired. Like graphemes, Unicode normalization rules can change with new releases of Unicode. As such, these functions are in separate packages instead of builtins (normalization is planned to be in [roc-lang/unicode](https://github.com/roc-lang/unicode) in the future, but it has not yet been implemented) so that updates to these functions based on new Unicode releases can happen without waiting on new releases of the Roc language.
##
## ### Capitalization
##
## We've already seen two examples of Unicode definitions that can change with new Unicode releases: graphemes and normalization. Another is capitalization; these rules can change with new Unicode releases (most often in the form of additions of new languages, but breaking changes to capitalization rules for existing languages are also possible), and so they are not included in builtin [`Str`](https://www.roc-lang.org/builtins/Str).
##
## This might seem particularly surprising, since capitalization functions are commonly included in standard libraries. However, it turns out that "capitalizing an arbitrary string" is impossible to do correctly without additional information.
##
## For example, what is the capitalized version of this string?
##
## ```
## "i"
## ```
##
## * In English, the correct answer is `"I"`.
## * In Turkish, the correct answer is `"İ"`.
##
## Similarly, the correct lowercased version of the string `"I"` is `"i"` in English and `"ı"` in Turkish.
##
## Turkish is not the only language to use this [dotless i](https://en.wikipedia.org/wiki/Dotless_I), and it's an example of how a function which capitalizes strings cannot give correct answers without the additional information of which language's capitalization rules should be used.
##
## Many languages defer to the operating system's [localization](https://en.wikipedia.org/wiki/Internationalization_and_localization) settings for this information. In that design, calling a program's capitalization function with an input string of `"i"` might give an answer of `"I"` on one machine and `"İ"` on a different machine, even though it was the same program running on both systems. Naturally, this can cause bugs—but more than that, writing tests to prevent bugs like this usually requires extra complexity compared to writing ordinary tests.
##
## In general, Roc programs should give the same answers for the same inputs even when run on different machines. There are exceptions to this (e.g. a program running out of system resources on one machine, while being able to make more progress on a machine that has more resources), but operating system's language localization is not among them.
##
## For these reasons, capitalization functions are not in [`Str`](https://www.roc-lang.org/builtins/Str). There is a planned `roc-lang` package to handle use cases like capitalization and sorting—sorting can also vary by language as well as by things like country—but implementation work has not yet started on this package.
##
## ### UTF-8
##
## Earlier, we discussed how Unicode code points can be described as [`U32`](https://www.roc-lang.org/builtins/Num#U32) integers. However, many common code points are very low integers, and can fit into a `U8` instead of needing an entire `U32` to represent them in memory. UTF-8 takes advantage of this, using a variable-width encoding to represent code points in 1-4 bytes, which saves a lot of memory in the typical case—especially compared to [UTF-16](https://en.wikipedia.org/wiki/UTF-16), which always uses at least 2 bytes to represent each code point, or [UTF-32](https://en.wikipedia.org/wiki/UTF-32), which always uses the maximum 4 bytes.
##
## This guide won't cover all the details of UTF-8, but the basic idea is this:
##
## - If a code point is 127 or lower, UTF-8 stores it in 1 byte.
## - If it's between 128 and 2047, UTF-8 stores it in 2 bytes.
## - If it's between 2048 and 65535, UTF-8 stores it in 3 bytes.
## - If it's higher than that, UTF-8 stores it in 4 bytes.
##
## The specific [UTF-8 encoding](https://en.wikipedia.org/wiki/UTF-8#Encoding) of these bytes involves using 1 to 5 bits of each byte for metadata about multi-byte sequences.
##
## A valuable feature of UTF-8 is that it is backwards-compatible with the [ASCII](https://en.wikipedia.org/wiki/ASCII) encoding that was widely used for many years. ASCII existed before Unicode did, and only used the integers 0 to 127 to represent its equivalent of code points. The Unicode code points 0 to 127 represent the same semantic information as ASCII, (e.g. the number 64 represents the letter "A" in both ASCII and in Unicode), and since UTF-8 represents code points 0 to 127 using one byte, all valid ASCII strings can be successfully parsed as UTF-8 without any need for conversion.
##
## Since many textual computer encodings—including [CSV](https://en.wikipedia.org/wiki/CSV), [XML](https://en.wikipedia.org/wiki/XML), and [JSON](https://en.wikipedia.org/wiki/JSON)—do not use any code points above 127 for their delimiters, it is often possible to write parsers for these formats using only `Str` functions which present UTF-8 as raw `U8` sequences, such as [`Str.walkUtf8`](https://www.roc-lang.org/builtins/Str#walkUtf8) and [`Str.toUtf8`](https://www.roc-lang.org/builtins/Str#toUtf8). In the typical case where they do not to need to parse out individual Unicode code points, they can get everything they need from `Str` UTF-8 functions without needing to depend on other packages.
##
## ### When to use code points, graphemes, and UTF-8
##
## Deciding when to use code points, graphemes, and UTF-8 can be nonobvious to say the least!
##
## The way Roc organizes the `Str` module and supporting packages is designed to help answer this question. Every situation is different, but the following rules of thumb are typical:
##
## * Most often, using `Str` values along with helper functions like [`split`](https://www.roc-lang.org/builtins/Str#split), [`joinWith`](https://www.roc-lang.org/builtins/Str#joinWith), and so on, is the best option.
## * If you are specifically implementing a parser, working in UTF-8 bytes is usually the best option. So functions like [`walkUtf8`](https://www.roc-lang.org/builtins/Str#walkUtf8), [toUtf8](https://www.roc-lang.org/builtins/Str#toUtf8), and so on. (Note that single-quote literals produce number literals, so ASCII-range literals like `'a'` gives an integer literal that works with a UTF-8 `U8`.)
## * If you are implementing a Unicode library like [roc-lang/unicode](https://github.com/roc-lang/unicode), working in terms of code points will be unavoidable. Aside from basic readability considerations like `\u(...)` in string literals, if you have the option to avoid working in terms of code points, it is almost always correct to avoid them.
## * If it seems like a good idea to split a string into "characters" (graphemes), you should definitely stop and reconsider whether this is really the best design. Almost always, doing this is some combination of more error-prone or slower (usually both) than doing something else that does not require taking graphemes into consideration.
##
## For this reason (among others), grapheme functions live in [roc-lang/unicode](https://github.com/roc-lang/unicode) rather than in [`Str`](https://www.roc-lang.org/builtins/Str). They are more niche than they seem, so they should not be reached for all the time!
##
## ## Performance
##
## This section deals with how Roc strings are represented in memory, and their performance characteristics.
##
## A normal heap-allocated roc `Str` is represented on the stack as:
## - A "capacity" unsigned integer, which respresents how many bytes are allocated on the heap to hold the string's contents.
## - A "length" unsigned integer, which rerepresents how many of the "capacity" bytes are actually in use. (A `Str` can have more bytes allocated on the heap than are actually in use.)
## - The memory address of the first byte in the string's actual contents.
##
## Each of these three fields is the same size: 64 bits on a 64-bit system, and 32 bits on a 32-bit system. The actual contents of the string are stored in one contiguous sequence of bytes, encoded as UTF-8, often on the heap but sometimes elsewhere—more on this later. Empty strings do not have heap allocations, so an empty `Str` on a 64-bit system still takes up 24 bytes on the stack (due to its three 64-bit fields).
##
## ### Reference counting and opportunistic mutation
##
## Like lists, dictionaries, and sets, Roc strings are automatically reference-counted and can benefit from opportunistic in-place mutation. The reference count is stored on the heap immediately before the first byte of the string's contents, and it has the same size as a memory address. This means it can count so high that it's impossible to write a Roc program which overflows a reference count, because having that many simultaneous references (each of which is a memory address) would have exhausted the operating system's address space first.
##
## When the string's reference count is 1, functions like [`Str.concat`](https://www.roc-lang.org/builtins/Str#concat) and [`Str.replaceEach`](https://www.roc-lang.org/builtins/Str#replaceEach) mutate the string in-place rather than allocating a new string. This preserves semantic immutability because it is unobservable in terms of the operation's output; if the reference count is 1, it means that memory would have otherwise been deallocated immediately anyway, and it's more efficient to reuse it instead of deallocating it and then immediately making a new allocation.
##
## The contents of statically-known strings (today that means string literals) are stored in the readonly section of the binary, so they do not need heap allocations or reference counts. They are not eligible for in-place mutation, since mutating the readonly section of the binary would cause an operating system [access violation](https://en.wikipedia.org/wiki/Segmentation_fault).
##
## ### Small String Optimization
##
## Roc uses a "small string optimization" when representing certain strings in memory.
##
## If you have a sufficiently long string, then on a 64-bit system it will be represented on the stack using 24 bytes, and on a 32-bit system it will take 12 bytes—plus however many bytes are in the string itself—on the heap. However, if there is a string shorter than either of these stack sizes (so, a string of up to 23 bytes on a 64-bit system, and up to 11 bytes on a 32-bit system), then that string will be stored entirely on the stack rather than having a separate heap allocation at all.
##
## This can be much more memory-efficient! However, `List` does not have this optimization (it has some runtime cost, and in the case of `List` it's not anticipated to come up nearly as often), which means when converting a small string to `List U8` it can result in a heap allocation.
##
## Note that this optimization is based entirely on how many UTF-8 bytes the string takes up in memory. It doesn't matter how many [graphemes](#graphemes), [code points](#code-points) or anything else it has; the only factor that determines whether a particular string is eligible for the small string optimization is the number of UTF-8 bytes it takes up in memory!
##
## ### Seamless Slices
##
## Try putting this into `roc repl`:
##
## ```
## » "foo/bar/baz" |> Str.split "/"
##
## ["foo", "bar", "baz"] : List Str
## ```
##
## All of these strings are small enough that the [small string optimization](#small) will apply, so none of them will be allocated on the heap.
##
## Now let's suppose they were long enough that this optimization no longer applied:
##
## ```
## » "a much, much, much, much/longer/string compared to the last one!" |> Str.split "/"
##
## ["a much, much, much, much", "longer", "string compared to the last one!"] : List Str
## ```
##
## Here, the only strings small enough for the small string optimization are `"/"` and `"longer"`. They will be allocated on the stack.
##
## The first and last strings in the returned list `"a much, much, much, much"` and `"string compared to the last one!"` will not be allocated on the heap either. Instead, they will be *seamless slices*, which means they will share memory with the original input string.
##
## * `"a much, much, much, much"` will share the first 24 bytes of the original string.
## * `"string compared to the last one!"` will share the last 32 bytes of the original string.
##
## All of these strings are semantically immutable, so sharing these bytes is an implementation detail that should only affect performance. By design, there is no way at either compile time or runtime to tell whether a string is a seamless slice. This allows the optimization's behavior to change in the future without affecting Roc programs' semantic behavior.
##
## Seamless slices create additional references to the original string, which make it ineligible for opportunistic mutation (along with the slices themselves; slices are never eligible for mutation), and which also make it take longer before the original string can be deallocated. A case where this might be noticeable in terms of performance would be:
## 1. A function takes a very large string as an argument and returns a much smaller slice into that string.
## 2. The smaller slice is used for a long time in the program, whereas the much larger original string stops being used.
## 3. In this situation, it might have been better for total program memory usage (although not necessarily overall performance) if the original large string could have been deallocated sooner, even at the expense of having to copy the smaller string into a new allocation instead of reusing the bytes with a seamless slice.
##
## If a situation like this comes up, a slice can be turned into a separate string by using [`Str.concat`](https://www.roc-lang.org/builtins/Str#concat) to concatenate the slice onto an empty string (or one created with [`Str.withCapacity`](https://www.roc-lang.org/builtins/Str#withCapacity)).
##
## Currently, the only way to get seamless slices of strings is by calling certain `Str` functions which return them. In general, `Str` functions which accept a string and return a subset of that string tend to do this. [`Str.trim`](https://www.roc-lang.org/builtins/Str#trim) is another example of a function which returns a seamless slice.
interface Str
exposes [
Utf8Problem,
@ -94,12 +335,9 @@ interface Str
joinWith,
split,
repeat,
countGraphemes,
countUtf8Bytes,
startsWithScalar,
toUtf8,
fromUtf8,
fromUtf8Range,
startsWith,
endsWith,
trim,
@ -108,7 +346,6 @@ interface Str
toDec,
toF64,
toF32,
toNat,
toU128,
toI128,
toU64,
@ -119,7 +356,6 @@ interface Str
toI16,
toU8,
toI8,
toScalars,
replaceEach,
replaceFirst,
replaceLast,
@ -129,19 +365,15 @@ interface Str
walkUtf8WithIndex,
reserve,
releaseExcessCapacity,
appendScalar,
walkScalars,
walkScalarsUntil,
withCapacity,
withPrefix,
graphemes,
contains,
]
imports [
Bool.{ Bool, Eq },
Result.{ Result },
List,
Num.{ Nat, Num, U8, U16, U32, U64, U128, I8, I16, I32, I64, I128, F32, F64, Dec },
Num.{ Num, U8, U16, U32, U64, U128, I8, I16, I32, I64, I128, F32, F64, Dec },
]
Utf8ByteProblem : [
@ -153,7 +385,7 @@ Utf8ByteProblem : [
EncodesSurrogateHalf,
]
Utf8Problem : { byteIndex : Nat, problem : Utf8ByteProblem }
Utf8Problem : { byteIndex : U64, problem : Utf8ByteProblem }
## Returns [Bool.true] if the string is empty, and [Bool.false] otherwise.
## ```
@ -194,7 +426,7 @@ concat : Str, Str -> Str
## the cost of using more memory than is necessary.
##
## For more details on how the performance optimization works, see [Str.reserve].
withCapacity : Nat -> Str
withCapacity : U64 -> Str
## Increase a string's capacity by at least the given number of additional bytes.
##
@ -252,7 +484,7 @@ withCapacity : Nat -> Str
## reallocations but will also waste a lot of memory!
##
## If you plan to use [Str.reserve] on an empty string, it's generally better to use [Str.withCapacity] instead.
reserve : Str, Nat -> Str
reserve : Str, U64 -> Str
## Combines a [List] of strings into a single string, with a separator
## string in between each.
@ -265,8 +497,7 @@ joinWith : List Str, Str -> Str
## Split a string around a separator.
##
## Passing `""` for the separator is not useful;
## it returns the original string wrapped in a [List]. To split a string
## into its individual [graphemes](https://stackoverflow.com/a/27331885/4200103), use `Str.graphemes`
## it returns the original string wrapped in a [List].
## ```
## expect Str.split "1,2,3" "," == ["1","2","3"]
## expect Str.split "1,2,3" "" == ["1,2,3"]
@ -283,79 +514,7 @@ split : Str, Str -> List Str
## expect Str.repeat "" 10 == ""
## expect Str.repeat "anything" 0 == ""
## ```
repeat : Str, Nat -> Str
## Counts the number of [extended grapheme clusters](http://www.unicode.org/glossary/#extended_grapheme_cluster)
## in the string.
##
## Note that the number of extended grapheme clusters can be different from the number
## of visual glyphs rendered! Consider the following examples:
## ```
## expect Str.countGraphemes "Roc" == 3
## expect Str.countGraphemes "👩‍👩‍👦‍👦" == 4
## expect Str.countGraphemes "🕊" == 1
## ```
## Note that "👩‍👩‍👦‍👦" takes up 4 graphemes (even though visually it appears as a single
## glyph) because under the hood it's represented using an emoji modifier sequence.
## In contrast, "🕊" only takes up 1 grapheme because under the hood it's represented
## using a single Unicode code point.
countGraphemes : Str -> Nat
## Split a string into its constituent graphemes.
##
## This function breaks a string into its individual [graphemes](https://stackoverflow.com/a/27331885/4200103),
## returning them as a list of strings. This is useful for working with text that
## contains complex characters, such as emojis.
##
## Examples:
## ```
## expect Str.graphemes "Roc" == ["R", "o", "c"]
## expect Str.graphemes "नमस्ते" == ["न", "म", "स्", "ते"]
## expect Str.graphemes "👩‍👩‍👦‍👦" == ["👩‍", "👩‍", "👦‍", "👦"]
## ```
##
## Note that the "👩‍👩‍👦‍👦" example consists of 4 grapheme clusters, although it visually
## appears as a single glyph. This is because it uses an emoji modifier sequence.
graphemes : Str -> List Str
## If the string begins with a [Unicode code point](http://www.unicode.org/glossary/#code_point)
## equal to the given [U32], returns [Bool.true]. Otherwise returns [Bool.false].
##
## If the given string is empty, or if the given [U32] is not a valid
## code point, returns [Bool.false].
## ```
## expect Str.startsWithScalar "鹏 means 'roc'" 40527 # "鹏" is Unicode scalar 40527
## expect !Str.startsWithScalar "9" 9 # the Unicode scalar for "9" is 57, not 9
## expect !Str.startsWithScalar "" 40527
## ```
##
## ## Performance Details
##
## This runs slightly faster than [Str.startsWith], so
## if you want to check whether a string begins with something that's representable
## in a single code point, you can use (for example) `Str.startsWithScalar '鹏'`
## instead of `Str.startsWith "鹏"`. ('鹏' evaluates to the [U32] value `40527`.)
## This will not work for graphemes which take up multiple code points, however;
## `Str.startsWithScalar '👩‍👩‍👦‍👦'` would be a compiler error because 👩‍👩‍👦‍👦 takes up
## multiple code points and cannot be represented as a single [U32].
## You'd need to use `Str.startsWithScalar "🕊"` instead.
startsWithScalar : Str, U32 -> Bool
## Returns a [List] of the [Unicode scalar values](https://unicode.org/glossary/#unicode_scalar_value)
## in the given string.
##
## (Roc strings contain only scalar values, not [surrogate code points](https://unicode.org/glossary/#surrogate_code_point),
## so this is equivalent to returning a list of the string's [code points](https://unicode.org/glossary/#code_point).)
## ```
## expect Str.toScalars "Roc" == [82, 111, 99]
## expect Str.toScalars "鹏" == [40527]
## expect Str.toScalars "சி" == [2970, 3007]
## expect Str.toScalars "🐦" == [128038]
## expect Str.toScalars "👩‍👩‍👦‍👦" == [128105, 8205, 128105, 8205, 128102, 8205, 128102]
## expect Str.toScalars "I ♥ Roc" == [73, 32, 9829, 32, 82, 111, 99]
## expect Str.toScalars "" == []
## ```
toScalars : Str -> List U32
repeat : Str, U64 -> Str
## Returns a [List] of the string's [U8] UTF-8 [code units](https://unicode.org/glossary/#code_unit).
## (To split the string into a [List] of smaller [Str] values instead of [U8] values,
@ -379,9 +538,9 @@ toUtf8 : Str -> List U8
## expect Str.fromUtf8 [] == Ok ""
## expect Str.fromUtf8 [255] |> Result.isErr
## ```
fromUtf8 : List U8 -> Result Str [BadUtf8 Utf8ByteProblem Nat]
fromUtf8 : List U8 -> Result Str [BadUtf8 Utf8ByteProblem U64]
fromUtf8 = \bytes ->
result = fromUtf8RangeLowlevel bytes 0 (List.len bytes)
result = fromUtf8Lowlevel bytes
if result.cIsOk then
Ok result.bString
@ -394,37 +553,14 @@ expect (Str.fromUtf8 [240, 159, 144, 166]) == Ok "🐦"
expect (Str.fromUtf8 []) == Ok ""
expect (Str.fromUtf8 [255]) |> Result.isErr
## Encode part of a [List] of [U8] UTF-8 [code units](https://unicode.org/glossary/#code_unit)
## into a [Str]
## ```
## expect Str.fromUtf8Range [72, 105, 80, 103] { start : 0, count : 2 } == Ok "Hi"
## ```
fromUtf8Range : List U8, { start : Nat, count : Nat } -> Result Str [BadUtf8 Utf8ByteProblem Nat, OutOfBounds]
fromUtf8Range = \bytes, config ->
if Num.addSaturated config.start config.count <= List.len bytes then
result = fromUtf8RangeLowlevel bytes config.start config.count
if result.cIsOk then
Ok result.bString
else
Err (BadUtf8 result.dProblemCode result.aByteIndex)
else
Err OutOfBounds
expect (Str.fromUtf8Range [72, 105, 80, 103] { start: 0, count: 2 }) == Ok "Hi"
expect (Str.fromUtf8Range [233, 185, 143, 224, 174, 154, 224, 174, 191] { start: 3, count: 3 }) == Ok "ச"
expect (Str.fromUtf8Range [240, 159, 144, 166] { start: 0, count: 4 }) == Ok "🐦"
expect (Str.fromUtf8Range [] { start: 0, count: 0 }) == Ok ""
expect (Str.fromUtf8Range [72, 105, 80, 103] { start: 2, count: 3 }) |> Result.isErr
FromUtf8Result : {
aByteIndex : Nat,
aByteIndex : U64,
bString : Str,
cIsOk : Bool,
dProblemCode : Utf8ByteProblem,
}
fromUtf8RangeLowlevel : List U8, Nat, Nat -> FromUtf8Result
fromUtf8Lowlevel : List U8 -> FromUtf8Result
## Check if the given [Str] starts with a value.
## ```
@ -489,25 +625,6 @@ toF64 = \string -> strToNumHelp string
toF32 : Str -> Result F32 [InvalidNumStr]
toF32 = \string -> strToNumHelp string
## Convert a [Str] to a [Nat]. If the given number doesn't fit in [Nat], it will be [truncated](https://www.ualberta.ca/computing-science/media-library/teaching-resources/java/truncation-rounding.html).
## [Nat] has a different maximum number depending on the system you're building
## for, so this may give a different answer on different systems.
##
## For example, on a 32-bit system, `Num.maxNat` will return the same answer as
## `Num.maxU32`. This means that calling `Str.toNat "9_000_000_000"` on a 32-bit
## system will return `Num.maxU32` instead of 9 billion, because 9 billion is
## larger than `Num.maxU32` and will not fit in a [Nat] on a 32-bit system.
##
## Calling `Str.toNat "9_000_000_000"` on a 64-bit system will return
## the [Nat] value of 9_000_000_000. This is because on a 64-bit system, [Nat] can
## hold up to `Num.maxU64`, and 9_000_000_000 is smaller than `Num.maxU64`.
## ```
## expect Str.toNat "9_000_000_000" == Ok 9000000000
## expect Str.toNat "not a number" == Err InvalidNumStr
## ```
toNat : Str -> Result Nat [InvalidNumStr]
toNat = \string -> strToNumHelp string
## Encode a [Str] to an unsigned [U128] integer. A [U128] value can hold numbers
## from `0` to `340_282_366_920_938_463_463_374_607_431_768_211_455` (over
## 340 undecillion). It can be specified with a u128 suffix.
@ -627,16 +744,16 @@ toI8 : Str -> Result I8 [InvalidNumStr]
toI8 = \string -> strToNumHelp string
## Get the byte at the given index, without performing a bounds check.
getUnsafe : Str, Nat -> U8
getUnsafe : Str, U64 -> U8
## Gives the number of bytes in a [Str] value.
## ```
## expect Str.countUtf8Bytes "Hello World" == 11
## ```
countUtf8Bytes : Str -> Nat
countUtf8Bytes : Str -> U64
## string slice that does not do bounds checking or utf-8 verification
substringUnsafe : Str, Nat, Nat -> Str
substringUnsafe : Str, U64, U64 -> Str
## Returns the given [Str] with each occurrence of a substring replaced.
## If the substring is not found, returns the original string.
@ -683,7 +800,7 @@ replaceFirst : Str, Str, Str -> Str
replaceFirst = \haystack, needle, flower ->
when splitFirst haystack needle is
Ok { before, after } ->
"\(before)\(flower)\(after)"
"$(before)$(flower)$(after)"
Err NotFound -> haystack
@ -701,7 +818,7 @@ replaceLast : Str, Str, Str -> Str
replaceLast = \haystack, needle, flower ->
when splitLast haystack needle is
Ok { before, after } ->
"\(before)\(flower)\(after)"
"$(before)$(flower)$(after)"
Err NotFound -> haystack
@ -744,7 +861,7 @@ expect splitFirst "hullabaloo" "ab" == Ok { before: "hull", after: "aloo" }
# splitFirst when needle is haystack
expect splitFirst "foo" "foo" == Ok { before: "", after: "" }
firstMatch : Str, Str -> [Some Nat, None]
firstMatch : Str, Str -> [Some U64, None]
firstMatch = \haystack, needle ->
haystackLength = Str.countUtf8Bytes haystack
needleLength = Str.countUtf8Bytes needle
@ -752,7 +869,7 @@ firstMatch = \haystack, needle ->
firstMatchHelp haystack needle 0 lastPossible
firstMatchHelp : Str, Str, Nat, Nat -> [Some Nat, None]
firstMatchHelp : Str, Str, U64, U64 -> [Some U64, None]
firstMatchHelp = \haystack, needle, index, lastPossible ->
if index <= lastPossible then
if matchesAt haystack index needle then
@ -795,7 +912,7 @@ expect Str.splitLast "hullabaloo" "ab" == Ok { before: "hull", after: "aloo" }
# splitLast when needle is haystack
expect Str.splitLast "foo" "foo" == Ok { before: "", after: "" }
lastMatch : Str, Str -> [Some Nat, None]
lastMatch : Str, Str -> [Some U64, None]
lastMatch = \haystack, needle ->
haystackLength = Str.countUtf8Bytes haystack
needleLength = Str.countUtf8Bytes needle
@ -803,7 +920,7 @@ lastMatch = \haystack, needle ->
lastMatchHelp haystack needle lastPossibleIndex
lastMatchHelp : Str, Str, Nat -> [Some Nat, None]
lastMatchHelp : Str, Str, U64 -> [Some U64, None]
lastMatchHelp = \haystack, needle, index ->
if matchesAt haystack index needle then
Some index
@ -817,7 +934,7 @@ lastMatchHelp = \haystack, needle, index ->
min = \x, y -> if x < y then x else y
matchesAt : Str, Nat, Str -> Bool
matchesAt : Str, U64, Str -> Bool
matchesAt = \haystack, haystackIndex, needle ->
haystackLength = Str.countUtf8Bytes haystack
needleLength = Str.countUtf8Bytes needle
@ -858,15 +975,15 @@ matchesAtHelp = \state ->
## state for each byte. The index for that byte in the string is provided
## to the update function.
## ```
## f : List U8, U8, Nat -> List U8
## f : List U8, U8, U64 -> List U8
## f = \state, byte, _ -> List.append state byte
## expect Str.walkUtf8WithIndex "ABC" [] f == [65, 66, 67]
## ```
walkUtf8WithIndex : Str, state, (state, U8, Nat -> state) -> state
walkUtf8WithIndex : Str, state, (state, U8, U64 -> state) -> state
walkUtf8WithIndex = \string, state, step ->
walkUtf8WithIndexHelp string state step 0 (Str.countUtf8Bytes string)
walkUtf8WithIndexHelp : Str, state, (state, U8, Nat -> state), Nat, Nat -> state
walkUtf8WithIndexHelp : Str, state, (state, U8, U64 -> state), U64, U64 -> state
walkUtf8WithIndexHelp = \string, state, step, index, length ->
if index < length then
byte = Str.getUnsafe string index
@ -890,7 +1007,7 @@ walkUtf8 : Str, state, (state, U8 -> state) -> state
walkUtf8 = \str, initial, step ->
walkUtf8Help str initial step 0 (Str.countUtf8Bytes str)
walkUtf8Help : Str, state, (state, U8 -> state), Nat, Nat -> state
walkUtf8Help : Str, state, (state, U8 -> state), U64, U64 -> state
walkUtf8Help = \str, state, step, index, length ->
if index < length then
byte = Str.getUnsafe str index
@ -907,80 +1024,6 @@ expect (walkUtf8 "鹏" [] List.append) == [233, 185, 143]
## Note: This will also convert seamless slices to regular lists.
releaseExcessCapacity : Str -> Str
## is UB when the scalar is invalid
appendScalarUnsafe : Str, U32 -> Str
## Append a [U32] scalar to the given string. If the given scalar is not a valid
## unicode value, it returns [Err InvalidScalar].
## ```
## expect Str.appendScalar "H" 105 == Ok "Hi"
## expect Str.appendScalar "😢" 0xabcdef == Err InvalidScalar
## ```
appendScalar : Str, U32 -> Result Str [InvalidScalar]
appendScalar = \string, scalar ->
if isValidScalar scalar then
Ok (appendScalarUnsafe string scalar)
else
Err InvalidScalar
isValidScalar : U32 -> Bool
isValidScalar = \scalar ->
scalar <= 0xD7FF || (scalar >= 0xE000 && scalar <= 0x10FFFF)
getScalarUnsafe : Str, Nat -> { scalar : U32, bytesParsed : Nat }
## Walks over the unicode [U32] values for the given [Str] and calls a function
## to update state for each.
## ```
## f : List U32, U32 -> List U32
## f = \state, scalar -> List.append state scalar
## expect Str.walkScalars "ABC" [] f == [65, 66, 67]
## ```
walkScalars : Str, state, (state, U32 -> state) -> state
walkScalars = \string, init, step ->
walkScalarsHelp string init step 0 (Str.countUtf8Bytes string)
walkScalarsHelp : Str, state, (state, U32 -> state), Nat, Nat -> state
walkScalarsHelp = \string, state, step, index, length ->
if index < length then
{ scalar, bytesParsed } = getScalarUnsafe string index
newState = step state scalar
walkScalarsHelp string newState step (Num.addWrap index bytesParsed) length
else
state
## Walks over the unicode [U32] values for the given [Str] and calls a function
## to update state for each.
## ```
## f : List U32, U32 -> [Break (List U32), Continue (List U32)]
## f = \state, scalar ->
## check = 66
## if scalar == check then
## Break [check]
## else
## Continue (List.append state scalar)
## expect Str.walkScalarsUntil "ABC" [] f == [66]
## expect Str.walkScalarsUntil "AxC" [] f == [65, 120, 67]
## ```
walkScalarsUntil : Str, state, (state, U32 -> [Break state, Continue state]) -> state
walkScalarsUntil = \string, init, step ->
walkScalarsUntilHelp string init step 0 (Str.countUtf8Bytes string)
walkScalarsUntilHelp : Str, state, (state, U32 -> [Break state, Continue state]), Nat, Nat -> state
walkScalarsUntilHelp = \string, state, step, index, length ->
if index < length then
{ scalar, bytesParsed } = getScalarUnsafe string index
when step state scalar is
Continue newState ->
walkScalarsUntilHelp string newState step (Num.addWrap index bytesParsed) length
Break newState ->
newState
else
state
strToNum : Str -> { berrorcode : U8, aresult : Num * }
strToNumHelp : Str -> Result (Num a) [InvalidNumStr]

View file

@ -34,7 +34,6 @@ interface TotallyNotJson
I128,
F32,
F64,
Nat,
Dec,
},
Bool.{ Bool, Eq },
@ -43,7 +42,7 @@ interface TotallyNotJson
## An opaque type with the `EncoderFormatting` and
## `DecoderFormatting` abilities.
Json := { fieldNameMapping : FieldNameMapping }
Json := {}
implements [
EncoderFormatting {
u8: encodeU8,
@ -89,21 +88,11 @@ Json := { fieldNameMapping : FieldNameMapping }
]
## Returns a JSON `Encoder` and `Decoder`
json = @Json { fieldNameMapping: Default }
json = @Json {}
## Returns a JSON `Encoder` and `Decoder` with configuration options
jsonWithOptions = \{ fieldNameMapping ? Default } ->
@Json { fieldNameMapping }
## Mapping between Roc record fields and JSON object names
FieldNameMapping : [
Default, # no transformation
SnakeCase, # snake_case
PascalCase, # PascalCase
KebabCase, # kabab-case
CamelCase, # camelCase
Custom (Str -> Str), # provide a custom formatting
]
jsonWithOptions = \{} ->
@Json {}
# TODO encode as JSON numbers as base 10 decimal digits
# e.g. the REPL `Num.toStr 12e42f64` gives
@ -171,14 +160,6 @@ encodeBool = \b ->
else
List.concat bytes (Str.toUtf8 "false")
# Test encode boolean
expect
input = [Bool.true, Bool.false]
actual = Encode.toBytes input json
expected = Str.toUtf8 "[true,false]"
actual == expected
encodeString = \str ->
Encode.custom \bytes, @Json {} ->
List.concat bytes (encodeStrBytes str)
@ -248,38 +229,10 @@ escapedByteToJson = \b ->
0x09 -> [0x5c, 'r'] # U+0009 Tab
_ -> [b]
expect escapedByteToJson '\n' == ['\\', 'n']
expect escapedByteToJson '\\' == ['\\', '\\']
expect escapedByteToJson '"' == ['\\', '"']
# Test encode small string
expect
input = "G'day"
actual = Encode.toBytes input json
expected = Str.toUtf8 "\"G'day\""
actual == expected
# Test encode large string
expect
input = "the quick brown fox jumps over the lazy dog"
actual = Encode.toBytes input json
expected = Str.toUtf8 "\"the quick brown fox jumps over the lazy dog\""
actual == expected
# Test encode with escapes e.g. "\r" encodes to "\\r"
expect
input = "the quick brown fox jumps over the lazy doga\r\nbc\\\"xz"
actual = Encode.toBytes input json
expected = Str.toUtf8 "\"the quick brown fox jumps over the lazy doga\\r\\nbc\\\\\\\"xz\""
actual == expected
encodeList = \lst, encodeElem ->
Encode.custom \bytes, @Json { fieldNameMapping } ->
Encode.custom \bytes, @Json {} ->
writeList = \{ buffer, elemsLeft }, elem ->
bufferWithElem = appendWith buffer (encodeElem elem) (@Json { fieldNameMapping })
bufferWithElem = appendWith buffer (encodeElem elem) (@Json {})
bufferWithSuffix =
if elemsLeft > 1 then
List.append bufferWithElem (Num.toU8 ',')
@ -293,27 +246,16 @@ encodeList = \lst, encodeElem ->
List.append withList (Num.toU8 ']')
# Test encode list of floats
expect
input : List F64
input = [-1, 0.00001, 1e12, 2.0e-2, 0.0003, 43]
actual = Encode.toBytes input json
expected = Str.toUtf8 "[-1,0.00001,1000000000000,0.02,0.0003,43]"
actual == expected
encodeRecord = \fields ->
Encode.custom \bytes, @Json { fieldNameMapping } ->
Encode.custom \bytes, @Json {} ->
writeRecord = \{ buffer, fieldsLeft }, { key, value } ->
fieldName = toObjectNameUsingMap key fieldNameMapping
fieldName = key
bufferWithKeyValue =
List.append buffer (Num.toU8 '"')
|> List.concat (Str.toUtf8 fieldName)
|> List.append (Num.toU8 '"')
|> List.append (Num.toU8 ':') # Note we need to encode using the json config here
|> appendWith value (@Json { fieldNameMapping })
|> appendWith value (@Json {})
bufferWithSuffix =
if fieldsLeft > 1 then
@ -328,52 +270,11 @@ encodeRecord = \fields ->
List.append bytesWithRecord (Num.toU8 '}')
# Test encode for a record with two strings ignoring whitespace
expect
input = { fruitCount: 2, ownerName: "Farmer Joe" }
encoder = jsonWithOptions { fieldNameMapping: PascalCase }
actual = Encode.toBytes input encoder
expected = Str.toUtf8 "{\"FruitCount\":2,\"OwnerName\":\"Farmer Joe\"}"
actual == expected
# Test encode of record with an array of strings and a boolean field
expect
input = { fruitFlavours: ["Apples", "Bananas", "Pears"], isFresh: Bool.true }
encoder = jsonWithOptions { fieldNameMapping: KebabCase }
actual = Encode.toBytes input encoder
expected = Str.toUtf8 "{\"fruit-flavours\":[\"Apples\",\"Bananas\",\"Pears\"],\"is-fresh\":true}"
actual == expected
# Test encode of record with a string and number field
expect
input = { firstSegment: "ab", secondSegment: 10u8 }
encoder = jsonWithOptions { fieldNameMapping: SnakeCase }
actual = Encode.toBytes input encoder
expected = Str.toUtf8 "{\"first_segment\":\"ab\",\"second_segment\":10}"
actual == expected
# Test encode of record of a record
expect
input = { outer: { inner: "a" }, other: { one: "b", two: 10u8 } }
encoder = jsonWithOptions { fieldNameMapping: Custom toYellingCase }
actual = Encode.toBytes input encoder
expected = Str.toUtf8 "{\"OTHER\":{\"ONE\":\"b\",\"TWO\":10},\"OUTER\":{\"INNER\":\"a\"}}"
actual == expected
toYellingCase = \str ->
Str.graphemes str
|> List.map toUppercase
|> Str.joinWith ""
encodeTuple = \elems ->
Encode.custom \bytes, @Json { fieldNameMapping } ->
Encode.custom \bytes, @Json {} ->
writeTuple = \{ buffer, elemsLeft }, elemEncoder ->
bufferWithElem =
appendWith buffer elemEncoder (@Json { fieldNameMapping })
appendWith buffer elemEncoder (@Json {})
bufferWithSuffix =
if elemsLeft > 1 then
@ -387,20 +288,11 @@ encodeTuple = \elems ->
{ buffer: bytesWithRecord } = List.walk elems { buffer: bytesHead, elemsLeft: List.len elems } writeTuple
List.append bytesWithRecord (Num.toU8 ']')
# Test encode of tuple
expect
input = ("The Answer is", 42)
actual = Encode.toBytes input json
expected = Str.toUtf8 "[\"The Answer is\",42]"
actual == expected
encodeTag = \name, payload ->
Encode.custom \bytes, @Json { fieldNameMapping } ->
Encode.custom \bytes, @Json {} ->
# Idea: encode `A v1 v2` as `{"A": [v1, v2]}`
writePayload = \{ buffer, itemsLeft }, encoder ->
bufferWithValue = appendWith buffer encoder (@Json { fieldNameMapping })
bufferWithValue = appendWith buffer encoder (@Json {})
bufferWithSuffix =
if itemsLeft > 1 then
List.append bufferWithValue (Num.toU8 ',')
@ -422,15 +314,6 @@ encodeTag = \name, payload ->
List.append bytesWithPayload (Num.toU8 ']')
|> List.append (Num.toU8 '}')
# Test encode of tag
expect
input = TheAnswer "is" 42
encoder = jsonWithOptions { fieldNameMapping: KebabCase }
actual = Encode.toBytes input encoder
expected = Str.toUtf8 "{\"TheAnswer\":[\"is\",42]}"
actual == expected
decodeU8 = Decode.custom \bytes, @Json {} ->
{ taken, rest } = takeJsonNumber bytes
@ -794,21 +677,21 @@ numberHelp = \state, byte ->
NumberState : [
Start,
Minus Nat,
Zero Nat,
Integer Nat,
FractionA Nat,
FractionB Nat,
ExponentA Nat,
ExponentB Nat,
ExponentC Nat,
Minus U64,
Zero U64,
Integer U64,
FractionA U64,
FractionB U64,
ExponentA U64,
ExponentB U64,
ExponentC U64,
Invalid,
Finish Nat,
Finish U64,
]
# TODO confirm if we would like to be able to decode
# "340282366920938463463374607431768211455" which is MAX U128 and 39 bytes
maxBytes : Nat
maxBytes : U64
maxBytes = 21 # Max bytes in a double precision float
isDigit0to9 : U8 -> Bool
@ -1010,13 +893,13 @@ stringHelp = \state, byte ->
StringState : [
Start,
Chars Nat,
Escaped Nat,
UnicodeA Nat,
UnicodeB Nat,
UnicodeC Nat,
UnicodeD Nat,
Finish Nat,
Chars U64,
Escaped U64,
UnicodeA U64,
UnicodeB U64,
UnicodeC U64,
UnicodeD U64,
Finish U64,
InvalidNumber,
]
@ -1292,14 +1175,14 @@ expect
actual == expected
ArrayOpeningState : [
BeforeOpeningBracket Nat,
AfterOpeningBracket Nat,
BeforeOpeningBracket U64,
AfterOpeningBracket U64,
]
ArrayClosingState : [
BeforeNextElemOrClosingBracket Nat,
BeforeNextElement Nat,
AfterClosingBracket Nat,
BeforeNextElemOrClosingBracket U64,
BeforeNextElement U64,
AfterClosingBracket U64,
]
# Test decoding an empty array
@ -1334,7 +1217,7 @@ expect
# JSON OBJECTS -----------------------------------------------------------------
decodeRecord = \initialState, stepField, finalizer -> Decode.custom \bytes, @Json { fieldNameMapping } ->
decodeRecord = \initialState, stepField, finalizer -> Decode.custom \bytes, @Json {} ->
# Recursively build up record from object field:value pairs
decodeFields = \recordState, bytesBeforeField ->
@ -1361,8 +1244,7 @@ decodeRecord = \initialState, stepField, finalizer -> Decode.custom \bytes, @Jso
# Decode the json value
{ val: updatedRecord, rest: bytesAfterValue } <-
(
fieldName =
fromObjectNameUsingMap objectName fieldNameMapping
fieldName = objectName
# Retrieve value decoder for the current field
when stepField recordState fieldName is
@ -1375,7 +1257,7 @@ decodeRecord = \initialState, stepField, finalizer -> Decode.custom \bytes, @Jso
Keep valueDecoder ->
# Decode the value using the decoder from the recordState
# Note we need to pass json config options recursively here
Decode.decodeWith valueBytes valueDecoder (@Json { fieldNameMapping })
Decode.decodeWith valueBytes valueDecoder (@Json {})
)
|> tryDecode
@ -1434,337 +1316,13 @@ objectHelp = \state, byte ->
_ -> Break InvalidObject
ObjectState : [
BeforeOpeningBrace Nat,
AfterOpeningBrace Nat,
ObjectFieldNameStart Nat,
BeforeColon Nat,
AfterColon Nat,
AfterObjectValue Nat,
AfterComma Nat,
AfterClosingBrace Nat,
BeforeOpeningBrace U64,
AfterOpeningBrace U64,
ObjectFieldNameStart U64,
BeforeColon U64,
AfterColon U64,
AfterObjectValue U64,
AfterComma U64,
AfterClosingBrace U64,
InvalidObject,
]
# Test decode of record with two strings ignoring whitespace
expect
input = Str.toUtf8 " {\n\"FruitCount\"\t:2\n, \"OwnerName\": \"Farmer Joe\" } "
decoder = jsonWithOptions { fieldNameMapping: PascalCase }
actual = Decode.fromBytesPartial input decoder
expected = Ok { fruitCount: 2, ownerName: "Farmer Joe" }
actual.result == expected
# Test decode of record with an array of strings and a boolean field
expect
input = Str.toUtf8 "{\"fruit-flavours\": [\"Apples\",\"Bananas\",\"Pears\"], \"is-fresh\": true }"
decoder = jsonWithOptions { fieldNameMapping: KebabCase }
actual = Decode.fromBytesPartial input decoder
expected = Ok { fruitFlavours: ["Apples", "Bananas", "Pears"], isFresh: Bool.true }
actual.result == expected
# Test decode of record with a string and number field
expect
input = Str.toUtf8 "{\"first_segment\":\"ab\",\"second_segment\":10}"
decoder = jsonWithOptions { fieldNameMapping: SnakeCase }
actual = Decode.fromBytesPartial input decoder
expected = Ok { firstSegment: "ab", secondSegment: 10u8 }
actual.result == expected
# Test decode of record of a record
expect
input = Str.toUtf8 "{\"OUTER\":{\"INNER\":\"a\"},\"OTHER\":{\"ONE\":\"b\",\"TWO\":10}}"
decoder = jsonWithOptions { fieldNameMapping: Custom fromYellingCase }
actual = Decode.fromBytesPartial input decoder
expected = Ok { outer: { inner: "a" }, other: { one: "b", two: 10u8 } }
actual.result == expected
fromYellingCase = \str ->
Str.graphemes str
|> List.map toLowercase
|> Str.joinWith ""
expect fromYellingCase "YELLING" == "yelling"
# Complex example from IETF RFC 8259 (2017)
complexExampleJson = Str.toUtf8 "{\"Image\":{\"Animated\":false,\"Height\":600,\"Ids\":[116,943,234,38793],\"Thumbnail\":{\"Height\":125,\"Url\":\"http:\\/\\/www.example.com\\/image\\/481989943\",\"Width\":100},\"Title\":\"View from 15th Floor\",\"Width\":800}}"
complexExampleRecord = {
image: {
width: 800,
height: 600,
title: "View from 15th Floor",
thumbnail: {
url: "http://www.example.com/image/481989943",
height: 125,
width: 100,
},
animated: Bool.false,
ids: [116, 943, 234, 38793],
},
}
# Test decode of Complex Example
expect
input = complexExampleJson
decoder = jsonWithOptions { fieldNameMapping: PascalCase }
actual = Decode.fromBytes input decoder
expected = Ok complexExampleRecord
actual == expected
# Test encode of Complex Example
expect
input = complexExampleRecord
encoder = jsonWithOptions { fieldNameMapping: PascalCase }
actual = Encode.toBytes input encoder
expected = complexExampleJson
actual == expected
fromObjectNameUsingMap : Str, FieldNameMapping -> Str
fromObjectNameUsingMap = \objectName, fieldNameMapping ->
when fieldNameMapping is
Default -> objectName
SnakeCase -> fromSnakeCase objectName
PascalCase -> fromPascalCase objectName
KebabCase -> fromKebabCase objectName
CamelCase -> fromCamelCase objectName
Custom transformation -> transformation objectName
toObjectNameUsingMap : Str, FieldNameMapping -> Str
toObjectNameUsingMap = \fieldName, fieldNameMapping ->
when fieldNameMapping is
Default -> fieldName
SnakeCase -> toSnakeCase fieldName
PascalCase -> toPascalCase fieldName
KebabCase -> toKebabCase fieldName
CamelCase -> toCamelCase fieldName
Custom transformation -> transformation fieldName
# Convert a `snake_case` JSON Object name to a Roc Field name
fromSnakeCase = \str ->
snakeToCamel str
# Convert a `PascalCase` JSON Object name to a Roc Field name
fromPascalCase = \str ->
pascalToCamel str
# Convert a `kabab-case` JSON Object name to a Roc Field name
fromKebabCase = \str ->
kebabToCamel str
# Convert a `camelCase` JSON Object name to a Roc Field name
fromCamelCase = \str ->
# Nothing to change as Roc field names are camelCase by default
str
# Convert a `camelCase` Roc Field name to a `snake_case` JSON Object name
toSnakeCase = \str ->
camelToSnake str
# Convert a `camelCase` Roc Field name to a `PascalCase` JSON Object name
toPascalCase = \str ->
camelToPascal str
# Convert a `camelCase` Roc Field name to a `kabab-case` JSON Object name
toKebabCase = \str ->
camelToKebeb str
# Convert a `camelCase` Roc Field name to a `camelCase` JSON Object name
toCamelCase = \str ->
# Nothing to change as Roc field names are camelCase by default
str
snakeToCamel : Str -> Str
snakeToCamel = \str ->
segments = Str.split str "_"
when segments is
[first, ..] ->
segments
|> List.dropFirst 1
|> List.map uppercaseFirst
|> List.prepend first
|> Str.joinWith ""
_ -> str
expect snakeToCamel "snake_case_string" == "snakeCaseString"
pascalToCamel : Str -> Str
pascalToCamel = \str ->
segments = Str.graphemes str
when segments is
[a, ..] ->
first = toLowercase a
rest = List.dropFirst segments 1
Str.joinWith (List.prepend rest first) ""
_ -> str
expect pascalToCamel "PascalCaseString" == "pascalCaseString"
kebabToCamel : Str -> Str
kebabToCamel = \str ->
segments = Str.split str "-"
when segments is
[first, ..] ->
segments
|> List.dropFirst 1
|> List.map uppercaseFirst
|> List.prepend first
|> Str.joinWith ""
_ -> str
expect kebabToCamel "kebab-case-string" == "kebabCaseString"
camelToPascal : Str -> Str
camelToPascal = \str ->
segments = Str.graphemes str
when segments is
[a, ..] ->
first = toUppercase a
rest = List.dropFirst segments 1
Str.joinWith (List.prepend rest first) ""
_ -> str
expect camelToPascal "someCaseString" == "SomeCaseString"
camelToKebeb : Str -> Str
camelToKebeb = \str ->
rest = Str.graphemes str
taken = List.withCapacity (List.len rest)
camelToKebabHelp { taken, rest }
|> .taken
|> Str.joinWith ""
camelToKebabHelp : { taken : List Str, rest : List Str } -> { taken : List Str, rest : List Str }
camelToKebabHelp = \{ taken, rest } ->
when rest is
[] -> { taken, rest }
[a, ..] if isUpperCase a ->
camelToKebabHelp {
taken: List.concat taken ["-", toLowercase a],
rest: List.dropFirst rest 1,
}
[a, ..] ->
camelToKebabHelp {
taken: List.append taken a,
rest: List.dropFirst rest 1,
}
expect camelToKebeb "someCaseString" == "some-case-string"
camelToSnake : Str -> Str
camelToSnake = \str ->
rest = Str.graphemes str
taken = List.withCapacity (List.len rest)
camelToSnakeHelp { taken, rest }
|> .taken
|> Str.joinWith ""
camelToSnakeHelp : { taken : List Str, rest : List Str } -> { taken : List Str, rest : List Str }
camelToSnakeHelp = \{ taken, rest } ->
when rest is
[] -> { taken, rest }
[a, ..] if isUpperCase a ->
camelToSnakeHelp {
taken: List.concat taken ["_", toLowercase a],
rest: List.dropFirst rest 1,
}
[a, ..] ->
camelToSnakeHelp {
taken: List.append taken a,
rest: List.dropFirst rest 1,
}
expect camelToSnake "someCaseString" == "some_case_string"
uppercaseFirst : Str -> Str
uppercaseFirst = \str ->
segments = Str.graphemes str
when segments is
[a, ..] ->
first = toUppercase a
rest = List.dropFirst segments 1
Str.joinWith (List.prepend rest first) ""
_ -> str
toUppercase : Str -> Str
toUppercase = \str ->
when str is
"a" -> "A"
"b" -> "B"
"c" -> "C"
"d" -> "D"
"e" -> "E"
"f" -> "F"
"g" -> "G"
"h" -> "H"
"i" -> "I"
"j" -> "J"
"k" -> "K"
"l" -> "L"
"m" -> "M"
"n" -> "N"
"o" -> "O"
"p" -> "P"
"q" -> "Q"
"r" -> "R"
"s" -> "S"
"t" -> "T"
"u" -> "U"
"v" -> "V"
"w" -> "W"
"x" -> "X"
"y" -> "Y"
"z" -> "Z"
_ -> str
toLowercase : Str -> Str
toLowercase = \str ->
when str is
"A" -> "a"
"B" -> "b"
"C" -> "c"
"D" -> "d"
"E" -> "e"
"F" -> "f"
"G" -> "g"
"H" -> "h"
"I" -> "i"
"J" -> "j"
"K" -> "k"
"L" -> "l"
"M" -> "m"
"N" -> "n"
"O" -> "o"
"P" -> "p"
"Q" -> "q"
"R" -> "r"
"S" -> "s"
"T" -> "t"
"U" -> "u"
"V" -> "v"
"W" -> "w"
"X" -> "x"
"Y" -> "y"
"Z" -> "z"
_ -> str
isUpperCase : Str -> Bool
isUpperCase = \str ->
when str is
"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" -> Bool.true
_ -> Bool.false

View file

@ -130,10 +130,10 @@ impl IntWidth {
// according to https://reviews.llvm.org/D28990#655487
//
// however, rust does not always think that this is true
// Our alignmets here are correct, but they will not match rust/zig/llvm until they update to llvm version 18.
match target_info.architecture {
Architecture::X86_64 => 16,
Architecture::Aarch64 | Architecture::Aarch32 | Architecture::Wasm32 => 16,
Architecture::X86_32 => 8,
Architecture::X86_64 | Architecture::Aarch64 | Architecture::X86_32 => 16,
Architecture::Aarch32 | Architecture::Wasm32 => 8,
}
}
}
@ -292,6 +292,10 @@ pub const NUM_FLOOR_F32: IntrinsicName = int_intrinsic!("roc_builtins.num.floor_
pub const NUM_FLOOR_F64: IntrinsicName = int_intrinsic!("roc_builtins.num.floor_f64");
pub const NUM_ROUND_F32: IntrinsicName = int_intrinsic!("roc_builtins.num.round_f32");
pub const NUM_ROUND_F64: IntrinsicName = int_intrinsic!("roc_builtins.num.round_f64");
pub const INT_TO_FLOAT_CAST_F32: IntrinsicName =
int_intrinsic!("roc_builtins.num.num_to_float_cast_f32");
pub const INT_TO_FLOAT_CAST_F64: IntrinsicName =
int_intrinsic!("roc_builtins.num.num_to_float_cast_f64");
pub const NUM_ADD_OR_PANIC_INT: IntrinsicName = int_intrinsic!("roc_builtins.num.add_or_panic");
pub const NUM_ADD_SATURATED_INT: IntrinsicName = int_intrinsic!("roc_builtins.num.add_saturated");
@ -331,23 +335,15 @@ pub const NUM_COUNT_TRAILING_ZERO_BITS: IntrinsicName =
int_intrinsic!("roc_builtins.num.count_trailing_zero_bits");
pub const NUM_COUNT_ONE_BITS: IntrinsicName = int_intrinsic!("roc_builtins.num.count_one_bits");
pub const NUM_BYTES_TO_U16: &str = "roc_builtins.num.bytes_to_u16";
pub const NUM_BYTES_TO_U32: &str = "roc_builtins.num.bytes_to_u32";
pub const NUM_BYTES_TO_U64: &str = "roc_builtins.num.bytes_to_u64";
pub const NUM_BYTES_TO_U128: &str = "roc_builtins.num.bytes_to_u128";
pub const STR_INIT: &str = "roc_builtins.str.init";
pub const STR_COUNT_SEGMENTS: &str = "roc_builtins.str.count_segments";
pub const STR_CONCAT: &str = "roc_builtins.str.concat";
pub const STR_JOIN_WITH: &str = "roc_builtins.str.joinWith";
pub const STR_SPLIT: &str = "roc_builtins.str.str_split";
pub const STR_TO_SCALARS: &str = "roc_builtins.str.to_scalars";
pub const STR_COUNT_GRAPEHEME_CLUSTERS: &str = "roc_builtins.str.count_grapheme_clusters";
pub const STR_COUNT_UTF8_BYTES: &str = "roc_builtins.str.count_utf8_bytes";
pub const STR_IS_EMPTY: &str = "roc_builtins.str.is_empty";
pub const STR_CAPACITY: &str = "roc_builtins.str.capacity";
pub const STR_STARTS_WITH: &str = "roc_builtins.str.starts_with";
pub const STR_STARTS_WITH_SCALAR: &str = "roc_builtins.str.starts_with_scalar";
pub const STR_ENDS_WITH: &str = "roc_builtins.str.ends_with";
pub const STR_NUMBER_OF_BYTES: &str = "roc_builtins.str.number_of_bytes";
pub const STR_FROM_INT: IntrinsicName = int_intrinsic!("roc_builtins.str.from_int");
@ -358,18 +354,15 @@ pub const STR_TO_DECIMAL: &str = "roc_builtins.str.to_decimal";
pub const STR_EQUAL: &str = "roc_builtins.str.equal";
pub const STR_SUBSTRING_UNSAFE: &str = "roc_builtins.str.substring_unsafe";
pub const STR_TO_UTF8: &str = "roc_builtins.str.to_utf8";
pub const STR_FROM_UTF8_RANGE: &str = "roc_builtins.str.from_utf8_range";
pub const STR_FROM_UTF8: &str = "roc_builtins.str.from_utf8";
pub const STR_REPEAT: &str = "roc_builtins.str.repeat";
pub const STR_TRIM: &str = "roc_builtins.str.trim";
pub const STR_TRIM_START: &str = "roc_builtins.str.trim_start";
pub const STR_TRIM_END: &str = "roc_builtins.str.trim_end";
pub const STR_GET_UNSAFE: &str = "roc_builtins.str.get_unsafe";
pub const STR_RESERVE: &str = "roc_builtins.str.reserve";
pub const STR_APPEND_SCALAR: &str = "roc_builtins.str.append_scalar";
pub const STR_GET_SCALAR_UNSAFE: &str = "roc_builtins.str.get_scalar_unsafe";
pub const STR_CLONE_TO: &str = "roc_builtins.str.clone_to";
pub const STR_WITH_CAPACITY: &str = "roc_builtins.str.with_capacity";
pub const STR_GRAPHEMES: &str = "roc_builtins.str.graphemes";
pub const STR_ALLOCATION_PTR: &str = "roc_builtins.str.allocation_ptr";
pub const STR_RELEASE_EXCESS_CAPACITY: &str = "roc_builtins.str.release_excess_capacity";
@ -386,6 +379,7 @@ pub const LIST_CONCAT: &str = "roc_builtins.list.concat";
pub const LIST_REPLACE: &str = "roc_builtins.list.replace";
pub const LIST_REPLACE_IN_PLACE: &str = "roc_builtins.list.replace_in_place";
pub const LIST_IS_UNIQUE: &str = "roc_builtins.list.is_unique";
pub const LIST_CLONE: &str = "roc_builtins.list.clone";
pub const LIST_PREPEND: &str = "roc_builtins.list.prepend";
pub const LIST_APPEND_UNSAFE: &str = "roc_builtins.list.append_unsafe";
pub const LIST_RESERVE: &str = "roc_builtins.list.reserve";
@ -409,6 +403,7 @@ pub const DEC_FROM_INT: IntrinsicName = int_intrinsic!("roc_builtins.dec.from_in
pub const DEC_FROM_STR: &str = "roc_builtins.dec.from_str";
pub const DEC_FROM_U64: &str = "roc_builtins.dec.from_u64";
pub const DEC_LOG: &str = "roc_builtins.dec.log";
pub const DEC_POW: &str = "roc_builtins.dec.pow";
pub const DEC_MUL_OR_PANIC: &str = "roc_builtins.dec.mul_or_panic";
pub const DEC_MUL_SATURATED: &str = "roc_builtins.dec.mul_saturated";
pub const DEC_MUL_WITH_OVERFLOW: &str = "roc_builtins.dec.mul_with_overflow";
@ -421,6 +416,9 @@ pub const DEC_SUB_WITH_OVERFLOW: &str = "roc_builtins.dec.sub_with_overflow";
pub const DEC_TAN: &str = "roc_builtins.dec.tan";
pub const DEC_TO_I128: &str = "roc_builtins.dec.to_i128";
pub const DEC_TO_STR: &str = "roc_builtins.dec.to_str";
pub const DEC_ROUND: IntrinsicName = int_intrinsic!("roc_builtins.dec.round");
pub const DEC_FLOOR: IntrinsicName = int_intrinsic!("roc_builtins.dec.floor");
pub const DEC_CEILING: IntrinsicName = int_intrinsic!("roc_builtins.dec.ceiling");
pub const UTILS_DBG_IMPL: &str = "roc_builtins.utils.dbg_impl";
pub const UTILS_TEST_PANIC: &str = "roc_builtins.utils.test_panic";
@ -445,6 +443,9 @@ pub const NOTIFY_PARENT_EXPECT: &str = "roc_builtins.utils.notify_parent_expect"
pub const UTILS_LONGJMP: &str = "longjmp";
pub const UTILS_SETJMP: &str = "setjmp";
pub const UTILS_WINDOWS_SETJMP: &str = "windows_setjmp";
pub const UTILS_WINDOWS_LONGJMP: &str = "windows_longjmp";
#[derive(Debug, Default)]
pub struct IntToIntrinsicName {
pub options: [IntrinsicName; 10],

View file

@ -33,7 +33,6 @@ macro_rules! map_symbol_to_lowlevel_and_arity {
Symbol::NUM_TO_U32 => Some(lowlevel_1(Symbol::NUM_TO_U32, LowLevel::NumIntCast, var_store)),
Symbol::NUM_TO_U64 => Some(lowlevel_1(Symbol::NUM_TO_U64, LowLevel::NumIntCast, var_store)),
Symbol::NUM_TO_U128 => Some(lowlevel_1(Symbol::NUM_TO_U128, LowLevel::NumIntCast, var_store)),
Symbol::NUM_TO_NAT => Some(lowlevel_1(Symbol::NUM_TO_NAT, LowLevel::NumIntCast, var_store)),
Symbol::NUM_INT_CAST => Some(lowlevel_1(Symbol::NUM_INT_CAST, LowLevel::NumIntCast, var_store)),
@ -50,7 +49,6 @@ macro_rules! map_symbol_to_lowlevel_and_arity {
Symbol::NUM_TO_U32_CHECKED => Some(to_num_checked(Symbol::NUM_TO_U32_CHECKED, var_store, LowLevel::NumToIntChecked)),
Symbol::NUM_TO_U64_CHECKED => Some(to_num_checked(Symbol::NUM_TO_U64_CHECKED, var_store, LowLevel::NumToIntChecked)),
Symbol::NUM_TO_U128_CHECKED => Some(to_num_checked(Symbol::NUM_TO_U128_CHECKED, var_store, LowLevel::NumToIntChecked)),
Symbol::NUM_TO_NAT_CHECKED => Some(to_num_checked(Symbol::NUM_TO_NAT_CHECKED, var_store, LowLevel::NumToIntChecked)),
Symbol::NUM_TO_F32_CHECKED => Some(to_num_checked(Symbol::NUM_TO_F32_CHECKED, var_store, LowLevel::NumToFloatChecked)),
Symbol::NUM_TO_F64_CHECKED => Some(to_num_checked(Symbol::NUM_TO_F64_CHECKED, var_store, LowLevel::NumToFloatChecked)),
@ -115,33 +113,28 @@ map_symbol_to_lowlevel_and_arity! {
StrJoinWith; STR_JOIN_WITH; 2,
StrIsEmpty; STR_IS_EMPTY; 1,
StrStartsWith; STR_STARTS_WITH; 2,
StrStartsWithScalar; STR_STARTS_WITH_SCALAR; 2,
StrEndsWith; STR_ENDS_WITH; 2,
StrSplit; STR_SPLIT; 2,
StrCountGraphemes; STR_COUNT_GRAPHEMES; 1,
StrCountUtf8Bytes; STR_COUNT_UTF8_BYTES; 1,
StrFromUtf8Range; STR_FROM_UTF8_RANGE_LOWLEVEL; 3,
StrFromUtf8; STR_FROM_UTF8_LOWLEVEL; 1,
StrToUtf8; STR_TO_UTF8; 1,
StrRepeat; STR_REPEAT; 2,
StrTrim; STR_TRIM; 1,
StrTrimStart; STR_TRIM_START; 1,
StrTrimEnd; STR_TRIM_END; 1,
StrToScalars; STR_TO_SCALARS; 1,
StrGetUnsafe; STR_GET_UNSAFE; 2,
StrSubstringUnsafe; STR_SUBSTRING_UNSAFE; 3,
StrReserve; STR_RESERVE; 2,
StrAppendScalar; STR_APPEND_SCALAR_UNSAFE; 2,
StrGetScalarUnsafe; STR_GET_SCALAR_UNSAFE; 2,
StrToNum; STR_TO_NUM; 1,
StrGetCapacity; STR_CAPACITY; 1,
StrWithCapacity; STR_WITH_CAPACITY; 1,
StrGraphemes; STR_GRAPHEMES; 1,
StrReleaseExcessCapacity; STR_RELEASE_EXCESS_CAPACITY; 1,
ListLen; LIST_LEN; 1,
ListLenUsize; LIST_LEN_USIZE; 1,
ListLenU64; LIST_LEN_U64; 1,
ListWithCapacity; LIST_WITH_CAPACITY; 1,
ListReserve; LIST_RESERVE; 2,
ListIsUnique; LIST_IS_UNIQUE; 1,
ListClone; LIST_CLONE; 1,
ListAppendUnsafe; LIST_APPEND_UNSAFE; 2,
ListPrepend; LIST_PREPEND; 2,
ListGetUnsafe; LIST_GET_UNSAFE; 2,
@ -178,9 +171,9 @@ map_symbol_to_lowlevel_and_arity! {
NumLte; NUM_LTE; 2,
NumCompare; NUM_COMPARE; 2,
NumDivFrac; NUM_DIV_FRAC; 2,
NumDivTruncUnchecked; NUM_DIV_TRUNC; 2,
NumDivTruncUnchecked; NUM_DIV_TRUNC_UNCHECKED; 2,
NumDivCeilUnchecked; NUM_DIV_CEIL; 2,
NumRemUnchecked; NUM_REM; 2,
NumRemUnchecked; NUM_REM_UNCHECKED; 2,
NumIsMultipleOf; NUM_IS_MULTIPLE_OF; 2,
NumAbs; NUM_ABS; 1,
NumNeg; NUM_NEG; 1,
@ -201,10 +194,6 @@ map_symbol_to_lowlevel_and_arity! {
NumAtan; NUM_ATAN; 1,
NumAcos; NUM_ACOS; 1,
NumAsin; NUM_ASIN; 1,
NumBytesToU16; NUM_BYTES_TO_U16_LOWLEVEL; 2,
NumBytesToU32; NUM_BYTES_TO_U32_LOWLEVEL; 2,
NumBytesToU64; NUM_BYTES_TO_U64_LOWLEVEL; 2,
NumBytesToU128; NUM_BYTES_TO_U128_LOWLEVEL; 2,
NumBitwiseAnd; NUM_BITWISE_AND; 2,
NumBitwiseXor; NUM_BITWISE_XOR; 2,
NumBitwiseOr; NUM_BITWISE_OR; 2,
@ -231,7 +220,7 @@ map_symbol_to_lowlevel_and_arity! {
/// Some builtins cannot be constructed in code gen alone, and need to be defined
/// as separate Roc defs. For example, List.get has this type:
///
/// List.get : List elem, Nat -> Result elem [OutOfBounds]*
/// List.get : List elem, U64 -> Result elem [OutOfBounds]*
///
/// Because this returns an open tag union for its Err type, it's not possible
/// for code gen to return a hardcoded value for OutOfBounds. For example,

View file

@ -1597,7 +1597,8 @@ fn canonicalize_closure_body<'a>(
enum MultiPatternVariables {
OnePattern,
MultiPattern {
bound_occurrences: VecMap<Symbol, (Region, u8)>,
num_patterns: usize,
bound_occurrences: VecMap<Symbol, (Region, usize)>,
},
}
@ -1606,7 +1607,8 @@ impl MultiPatternVariables {
fn new(num_patterns: usize) -> Self {
if num_patterns > 1 {
Self::MultiPattern {
bound_occurrences: VecMap::with_capacity(2),
num_patterns,
bound_occurrences: VecMap::with_capacity(num_patterns),
}
} else {
Self::OnePattern
@ -1617,7 +1619,9 @@ impl MultiPatternVariables {
fn add_pattern(&mut self, pattern: &Loc<Pattern>) {
match self {
MultiPatternVariables::OnePattern => {}
MultiPatternVariables::MultiPattern { bound_occurrences } => {
MultiPatternVariables::MultiPattern {
bound_occurrences, ..
} => {
for (sym, region) in BindingsFromPattern::new(pattern) {
if !bound_occurrences.contains_key(&sym) {
bound_occurrences.insert(sym, (region, 0));
@ -1630,15 +1634,18 @@ impl MultiPatternVariables {
#[inline(always)]
fn get_unbound(self) -> impl Iterator<Item = (Symbol, Region)> {
let bound_occurrences = match self {
MultiPatternVariables::OnePattern => Default::default(),
MultiPatternVariables::MultiPattern { bound_occurrences } => bound_occurrences,
let (bound_occurrences, num_patterns) = match self {
MultiPatternVariables::OnePattern => (Default::default(), 1),
MultiPatternVariables::MultiPattern {
bound_occurrences,
num_patterns,
} => (bound_occurrences, num_patterns),
};
bound_occurrences
.into_iter()
.filter_map(|(sym, (region, occurs))| {
if occurs == 1 {
.filter_map(move |(sym, (region, occurs))| {
if occurs != num_patterns {
Some((sym, region))
} else {
None
@ -2600,10 +2607,38 @@ fn flatten_str_lines<'a>(
fn desugar_str_segments(var_store: &mut VarStore, segments: Vec<StrSegment>) -> Expr {
use StrSegment::*;
let n = segments.len();
let mut iter = segments.into_iter().rev();
let mut loc_expr = match iter.next() {
Some(Plaintext(string)) => Loc::at(Region::zero(), Expr::Str(string)),
Some(Interpolation(loc_expr)) => loc_expr,
Some(Interpolation(loc_expr)) => {
if n == 1 {
// We concat with the empty string to ensure a type error when loc_expr is not a string
let empty_string = Loc::at(Region::zero(), Expr::Str("".into()));
let fn_expr = Loc::at(
Region::zero(),
Expr::Var(Symbol::STR_CONCAT, var_store.fresh()),
);
let expr = Expr::Call(
Box::new((
var_store.fresh(),
fn_expr,
var_store.fresh(),
var_store.fresh(),
)),
vec![
(var_store.fresh(), empty_string),
(var_store.fresh(), loc_expr),
],
CalledVia::StringInterpolation,
);
Loc::at(Region::zero(), expr)
} else {
loc_expr
}
}
None => {
// No segments? Empty string!

View file

@ -1051,14 +1051,13 @@ fn fix_values_captured_in_closure_expr(
debug_assert!(!captures.is_empty());
captured_symbols.extend(captures);
captured_symbols.swap_remove(i);
// Jump two, because the next element is now one of the newly-added captures,
// which we don't need to check.
i += 2;
added_captures = true;
} else {
i += 1;
}
// Always jump one, because the current element either does not have captures or
// is now one of the newly-added captures, which we don't need to check.
i += 1;
}
if added_captures {
// Re-sort, since we've added new captures.

View file

@ -198,7 +198,6 @@ fn parse_literal_suffix(num_str: &str) -> (Option<ParsedWidth>, &str) {
"i32", ParsedWidth::Int(IntLitWidth::I32)
"i64", ParsedWidth::Int(IntLitWidth::I64)
"i128", ParsedWidth::Int(IntLitWidth::I128)
"nat", ParsedWidth::Int(IntLitWidth::Nat)
"dec", ParsedWidth::Float(FloatWidth::Dec)
"f32", ParsedWidth::Float(FloatWidth::F32)
"f64", ParsedWidth::Float(FloatWidth::F64)

View file

@ -594,7 +594,7 @@ pub fn desugar_expr<'a>(
// line_info is an option so that we can lazily calculate it.
// That way it there are no `dbg` statements, we never pay the cast of scanning the source an extra time.
if matches!(line_info, None) {
if line_info.is_none() {
*line_info = Some(LineInfo::new(src));
}
let line_col = line_info.as_ref().unwrap().convert_pos(region.start());

View file

@ -13,7 +13,7 @@ use crate::{
},
pattern::{DestructType, Pattern, RecordDestruct, TupleDestruct},
};
#[derive(Clone)]
pub enum DeclarationInfo<'a> {
Value {
loc_symbol: Loc<Symbol>,
@ -164,7 +164,7 @@ pub fn walk_decls<V: Visitor>(visitor: &mut V, decls: &Declarations) {
}
}
fn walk_decl<V: Visitor>(visitor: &mut V, decl: DeclarationInfo<'_>) {
pub fn walk_decl<V: Visitor>(visitor: &mut V, decl: DeclarationInfo<'_>) {
use DeclarationInfo::*;
match decl {

View file

@ -1,17 +0,0 @@
/node_modules
/.pnp
.pnp.js
/coverage
/build
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

View file

@ -173,4 +173,7 @@ flags! {
/// Don't build and use the subs cache (speeds up compilation of load and previous crates)
ROC_SKIP_SUBS_CACHE
/// Print out shell commands used to buid the Roc and host code
ROC_PRINT_BUILD_COMMANDS
}

View file

@ -639,11 +639,11 @@ fn step_elem(
WhenBranch {
patterns: vec![WhenBranchPattern {
pattern: Loc::at_zero(Pattern::IntLiteral(
Variable::NAT,
Variable::NATURAL,
Variable::U64,
Variable::UNSIGNED64,
index.to_string().into_boxed_str(),
IntValue::I128((index as i128).to_ne_bytes()),
IntBound::Exact(IntLitWidth::Nat),
IntBound::Exact(IntLitWidth::U64),
)),
degenerate: false,
}],
@ -676,12 +676,12 @@ fn step_elem(
// when index is
let body = Expr::When {
loc_cond: Box::new(Loc::at_zero(Expr::Var(index_arg_symbol, Variable::NAT))),
cond_var: Variable::NAT,
loc_cond: Box::new(Loc::at_zero(Expr::Var(index_arg_symbol, Variable::U64))),
cond_var: Variable::U64,
expr_var: keep_or_skip_var,
region: Region::zero(),
branches,
branches_cond_var: Variable::NAT,
branches_cond_var: Variable::U64,
exhaustive: ExhaustiveMark::known_exhaustive(),
};
@ -699,7 +699,7 @@ fn step_elem(
};
{
let args_slice = SubsSlice::insert_into_subs(env.subs, [state_record_var, Variable::NAT]);
let args_slice = SubsSlice::insert_into_subs(env.subs, [state_record_var, Variable::U64]);
env.subs.set_content(
function_type,
@ -721,7 +721,7 @@ fn step_elem(
Loc::at_zero(Pattern::Identifier(state_arg_symbol)),
),
(
Variable::NAT,
Variable::U64,
AnnotatedMark::known_exhaustive(),
Loc::at_zero(Pattern::Identifier(index_arg_symbol)),
),

View file

@ -129,7 +129,6 @@ const fn from_builtin_symbol(symbol: Symbol) -> Option<Result<FlatDecodable, Der
Symbol::NUM_DEC | Symbol::NUM_DECIMAL => Some(Ok(Immediate(Symbol::DECODE_DEC))),
Symbol::NUM_F32 | Symbol::NUM_BINARY32 => Some(Ok(Immediate(Symbol::DECODE_F32))),
Symbol::NUM_F64 | Symbol::NUM_BINARY64 => Some(Ok(Immediate(Symbol::DECODE_F64))),
Symbol::NUM_NAT | Symbol::NUM_NATURAL => Some(Err(DeriveError::Underivable)),
_ => None,
}
}

View file

@ -163,7 +163,6 @@ const fn from_builtin_symbol(symbol: Symbol) -> Option<Result<FlatEncodable, Der
Symbol::NUM_DEC | Symbol::NUM_DECIMAL => Some(Ok(Immediate(Symbol::ENCODE_DEC))),
Symbol::NUM_F32 | Symbol::NUM_BINARY32 => Some(Ok(Immediate(Symbol::ENCODE_F32))),
Symbol::NUM_F64 | Symbol::NUM_BINARY64 => Some(Ok(Immediate(Symbol::ENCODE_F64))),
Symbol::NUM_NAT | Symbol::NUM_NATURAL => Some(Err(DeriveError::Underivable)),
_ => None,
}
}

View file

@ -191,9 +191,6 @@ const fn builtin_symbol_to_hash_lambda(symbol: Symbol) -> Option<FlatHash> {
Symbol::NUM_I128 | Symbol::NUM_SIGNED128 => {
Some(SingleLambdaSetImmediate(Symbol::HASH_HASH_I128))
}
Symbol::NUM_NAT | Symbol::NUM_NATURAL => {
Some(SingleLambdaSetImmediate(Symbol::HASH_HASH_NAT))
}
Symbol::NUM_DEC | Symbol::NUM_DECIMAL => {
Some(SingleLambdaSetImmediate(Symbol::HASH_HASH_DEC))
}

View file

@ -204,7 +204,6 @@ impl FlatInspectable {
Symbol::NUM_DEC | Symbol::NUM_DECIMAL => Some(Immediate(Symbol::INSPECT_DEC)),
Symbol::NUM_F32 | Symbol::NUM_BINARY32 => Some(Immediate(Symbol::INSPECT_F32)),
Symbol::NUM_F64 | Symbol::NUM_BINARY64 => Some(Immediate(Symbol::INSPECT_F64)),
Symbol::NUM_NAT | Symbol::NUM_NATURAL => Some(Immediate(Symbol::INSPECT_NAT)),
_ => None,
}
}

View file

@ -154,7 +154,9 @@ fn fmt_comment(buf: &mut Buf, comment: &str) {
}
buf.push('#');
if !comment.starts_with(' ') {
// Add a space between the starting `#` and the rest of the comment,
// unless there already is a space or the comment is of the form `#### something`.
if !comment.starts_with(' ') && !comment.starts_with('#') {
buf.spaces(1);
}
buf.push_str(comment.trim_end());

View file

@ -1,3 +1,6 @@
#![allow(clippy::redundant_closure_call)]
//|> clippy false positive: https://github.com/rust-lang/rust-clippy/issues/1553
use crate::generic64::{storage::StorageManager, Assembler, CallConv, RegTrait};
use crate::{
pointer_layouts, single_register_floats, single_register_int_builtins,
@ -1242,6 +1245,16 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
fabs_freg_freg(buf, FloatWidth::F64, dst, src);
}
#[inline(always)]
fn abs_freg32_freg32(
buf: &mut Vec<'_, u8>,
_relocs: &mut Vec<'_, Relocation>,
dst: AArch64FloatReg,
src: AArch64FloatReg,
) {
fabs_freg_freg(buf, FloatWidth::F32, dst, src);
}
#[inline(always)]
fn add_reg64_reg64_imm32(
buf: &mut Vec<'_, u8>,
@ -1266,6 +1279,7 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
) {
add_reg64_reg64_reg64(buf, dst, src1, src2);
}
#[inline(always)]
fn add_freg32_freg32_freg32(
buf: &mut Vec<'_, u8>,
@ -1285,6 +1299,25 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
fadd_freg_freg_freg(buf, FloatWidth::F64, dst, src1, src2);
}
#[inline(always)]
fn sub_freg32_freg32_freg32(
buf: &mut Vec<'_, u8>,
dst: AArch64FloatReg,
src1: AArch64FloatReg,
src2: AArch64FloatReg,
) {
fsub_freg_freg_freg(buf, FloatWidth::F32, dst, src1, src2);
}
#[inline(always)]
fn sub_freg64_freg64_freg64(
buf: &mut Vec<'_, u8>,
dst: AArch64FloatReg,
src1: AArch64FloatReg,
src2: AArch64FloatReg,
) {
fsub_freg_freg_freg(buf, FloatWidth::F64, dst, src1, src2);
}
#[inline(always)]
fn call(buf: &mut Vec<'_, u8>, relocs: &mut Vec<'_, Relocation>, fn_name: String) {
let inst = 0b1001_0100_0000_0000_0000_0000_0000_0000u32;
@ -1638,6 +1671,11 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
Self::mov_freg64_mem64_offset32(buf, dst, AArch64GeneralReg::FP, offset)
}
#[inline(always)]
fn mov_freg32_base32(buf: &mut Vec<'_, u8>, dst: AArch64FloatReg, offset: i32) {
Self::mov_freg32_mem32_offset32(buf, dst, AArch64GeneralReg::FP, offset)
}
#[inline(always)]
fn mov_reg_mem_offset32(
buf: &mut Vec<'_, u8>,
@ -3894,6 +3932,27 @@ fn fadd_freg_freg_freg(
buf.extend(inst.bytes());
}
/// `FSUB Sd/Dd, Sn/Dn, Sm/Dm` -> Sub Sn/Dn and Sm/Dm and place the result into Sd/Dd.
#[inline(always)]
fn fsub_freg_freg_freg(
buf: &mut Vec<'_, u8>,
ftype: FloatWidth,
dst: AArch64FloatReg,
src1: AArch64FloatReg,
src2: AArch64FloatReg,
) {
let inst =
FloatingPointDataProcessingTwoSource::new(FloatingPointDataProcessingTwoSourceParams {
opcode: 0b0011,
ptype: ftype,
rd: dst,
rn: src1,
rm: src2,
});
buf.extend(inst.bytes());
}
/// `FCMP Sn/Dn, Sm/Dm` -> Compare Sn/Dn and Sm/Dm, setting condition flags.
#[inline(always)]
fn fcmp_freg_freg(

View file

@ -1,3 +1,6 @@
#![allow(clippy::redundant_closure_call)]
//|> clippy false positive: https://github.com/rust-lang/rust-clippy/issues/1553
pub fn merge_instructions_without_line_numbers(instructions: capstone::Instructions) -> String {
instructions
.iter()

View file

@ -164,8 +164,21 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait>: Sized + Copy {
dst: FloatReg,
src: FloatReg,
);
fn abs_freg32_freg32(
buf: &mut Vec<'_, u8>,
relocs: &mut Vec<'_, Relocation>,
dst: FloatReg,
src: FloatReg,
);
fn add_reg64_reg64_imm32(buf: &mut Vec<'_, u8>, dst: GeneralReg, src1: GeneralReg, imm32: i32);
fn add_reg64_reg64_reg64(
buf: &mut Vec<'_, u8>,
dst: GeneralReg,
src1: GeneralReg,
src2: GeneralReg,
);
fn add_freg32_freg32_freg32(
buf: &mut Vec<'_, u8>,
dst: FloatReg,
@ -178,12 +191,6 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait>: Sized + Copy {
src1: FloatReg,
src2: FloatReg,
);
fn add_reg64_reg64_reg64(
buf: &mut Vec<'_, u8>,
dst: GeneralReg,
src1: GeneralReg,
src2: GeneralReg,
);
fn and_reg64_reg64_reg64(
buf: &mut Vec<'_, u8>,
@ -351,6 +358,7 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait>: Sized + Copy {
// base32 is similar to stack based instructions but they reference the base/frame pointer.
fn mov_freg64_base32(buf: &mut Vec<'_, u8>, dst: FloatReg, offset: i32);
fn mov_freg32_base32(buf: &mut Vec<'_, u8>, dst: FloatReg, offset: i32);
fn mov_reg_base32(
buf: &mut Vec<'_, u8>,
@ -629,6 +637,19 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait>: Sized + Copy {
ASM: Assembler<GeneralReg, FloatReg>,
CC: CallConv<GeneralReg, FloatReg, ASM>;
fn sub_freg32_freg32_freg32(
buf: &mut Vec<'_, u8>,
dst: FloatReg,
src1: FloatReg,
src2: FloatReg,
);
fn sub_freg64_freg64_freg64(
buf: &mut Vec<'_, u8>,
dst: FloatReg,
src1: FloatReg,
src2: FloatReg,
);
fn sub_reg64_reg64_imm32(buf: &mut Vec<'_, u8>, dst: GeneralReg, src1: GeneralReg, imm32: i32);
fn sub_reg64_reg64_reg64(
buf: &mut Vec<'_, u8>,
@ -1302,22 +1323,25 @@ impl<
let src_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src);
ASM::abs_freg64_freg64(&mut self.buf, &mut self.relocs, dst_reg, src_reg);
}
LayoutRepr::Builtin(Builtin::Float(FloatWidth::F32)) => {
let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst);
let src_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src);
ASM::abs_freg32_freg32(&mut self.buf, &mut self.relocs, dst_reg, src_reg);
}
x => todo!("NumAbs: layout, {:?}", x),
}
}
fn build_num_add(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &InLayout<'a>) {
match self.layout_interner.get_repr(*layout) {
LayoutRepr::Builtin(Builtin::Int(quadword_and_smaller!())) => {
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
let src1_reg = self
.storage_manager
.load_to_general_reg(&mut self.buf, src1);
let src2_reg = self
.storage_manager
.load_to_general_reg(&mut self.buf, src2);
ASM::add_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
}
LayoutRepr::Builtin(Builtin::Int(int_width)) => self.build_fn_call(
dst,
bitcode::NUM_ADD_OR_PANIC_INT[int_width].to_string(),
&[*src1, *src2],
&[*layout, *layout],
layout,
),
LayoutRepr::Builtin(Builtin::Float(FloatWidth::F64)) => {
let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst);
let src1_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src1);
@ -1330,16 +1354,60 @@ impl<
let src2_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src2);
ASM::add_freg32_freg32_freg32(&mut self.buf, dst_reg, src1_reg, src2_reg);
}
LayoutRepr::Builtin(Builtin::Decimal) => {
self.build_fn_call(
dst,
bitcode::DEC_ADD_OR_PANIC.to_string(),
&[*src1, *src2],
&[Layout::DEC, Layout::DEC],
&Layout::DEC,
);
LayoutRepr::DEC => self.build_fn_call(
dst,
bitcode::DEC_ADD_OR_PANIC.to_string(),
&[*src1, *src2],
&[Layout::DEC, Layout::DEC],
&Layout::DEC,
),
other => unreachable!("NumAdd for layout {other:?}"),
}
}
fn build_num_add_wrap(
&mut self,
dst: &Symbol,
src1: &Symbol,
src2: &Symbol,
layout: &InLayout<'a>,
) {
match self.layout_interner.get_repr(*layout) {
LayoutRepr::Builtin(Builtin::Int(quadword_and_smaller!())) => {
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
let src1_reg = self
.storage_manager
.load_to_general_reg(&mut self.buf, src1);
let src2_reg = self
.storage_manager
.load_to_general_reg(&mut self.buf, src2);
ASM::add_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
}
x => todo!("NumAdd: layout, {:?}", x),
LayoutRepr::Builtin(Builtin::Float(FloatWidth::F64)) => {
let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst);
let src1_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src1);
let src2_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src2);
ASM::add_freg64_freg64_freg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
}
LayoutRepr::Builtin(Builtin::Float(FloatWidth::F32)) => {
let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst);
let src1_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src1);
let src2_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src2);
ASM::add_freg32_freg32_freg32(&mut self.buf, dst_reg, src1_reg, src2_reg);
}
LayoutRepr::DEC => self.build_fn_call(
dst,
bitcode::DEC_ADD_SATURATED.to_string(),
&[*src1, *src2],
&[Layout::DEC, Layout::DEC],
&Layout::DEC,
),
other => unreachable!("NumAddWrap for layout {other:?}"),
}
}
@ -1638,6 +1706,15 @@ impl<
let src2_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src2);
ASM::div_freg32_freg32_freg32(&mut self.buf, dst_reg, src1_reg, src2_reg);
}
LayoutRepr::Builtin(Builtin::Decimal) => {
self.build_fn_call(
dst,
bitcode::DEC_DIV.to_string(),
&[*src1, *src2],
&[*layout, *layout],
layout,
);
}
x => todo!("NumDiv: layout, {:?}", x),
}
}
@ -1717,9 +1794,38 @@ impl<
}
fn build_num_sub(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &InLayout<'a>) {
// for the time being, `num_sub` is implemented as wrapping subtraction. In roc, the normal
// `sub` should panic on overflow, but we just don't do that yet
self.build_num_sub_wrap(dst, src1, src2, layout)
match self.layout_interner.get_repr(*layout) {
LayoutRepr::Builtin(Builtin::Int(int_width)) => self.build_fn_call(
dst,
bitcode::NUM_SUB_OR_PANIC_INT[int_width].to_string(),
&[*src1, *src2],
&[*layout, *layout],
layout,
),
LayoutRepr::Builtin(Builtin::Float(FloatWidth::F64)) => {
let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst);
let src1_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src1);
let src2_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src2);
ASM::sub_freg64_freg64_freg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
}
LayoutRepr::Builtin(Builtin::Float(FloatWidth::F32)) => {
let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst);
let src1_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src1);
let src2_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src2);
ASM::sub_freg32_freg32_freg32(&mut self.buf, dst_reg, src1_reg, src2_reg);
}
LayoutRepr::DEC => self.build_fn_call(
dst,
bitcode::DEC_SUB_OR_PANIC.to_string(),
&[*src1, *src2],
&[Layout::DEC, Layout::DEC],
&Layout::DEC,
),
other => unreachable!("NumMul for layout {other:?}"),
}
}
fn build_num_sub_wrap(
@ -2049,6 +2155,30 @@ impl<
);
}
fn build_int_to_float_cast(
&mut self,
dst: &Symbol,
src: &Symbol,
int_width: IntWidth,
float_width: FloatWidth,
) {
use roc_builtins::bitcode::{INT_TO_FLOAT_CAST_F32, INT_TO_FLOAT_CAST_F64};
self.build_fn_call(
dst,
match float_width {
FloatWidth::F32 => INT_TO_FLOAT_CAST_F32[int_width].to_string(),
FloatWidth::F64 => INT_TO_FLOAT_CAST_F64[int_width].to_string(),
},
&[*src],
&[Layout::from_int_width(int_width)],
match float_width {
FloatWidth::F32 => &Layout::F32,
FloatWidth::F64 => &Layout::F64,
},
);
}
fn build_num_cmp(
&mut self,
dst: &Symbol,
@ -2700,8 +2830,62 @@ impl<
}
}
fn build_list_len(&mut self, dst: &Symbol, list: &Symbol) {
self.storage_manager.list_len(&mut self.buf, dst, list);
fn build_list_len_usize(&mut self, dst: &Symbol, list: &Symbol) {
self.storage_manager
.list_len_usize(&mut self.buf, dst, list);
}
fn build_list_len_u64(&mut self, dst: &Symbol, list: &Symbol) {
self.storage_manager.list_len_u64(&mut self.buf, dst, list);
}
fn build_list_clone(
&mut self,
dst: Symbol,
input_list: Symbol,
elem_layout: InLayout<'a>,
ret_layout: InLayout<'a>,
) {
// List alignment argument (u32).
self.load_layout_alignment(ret_layout, Symbol::DEV_TMP);
// Load element_width argument (usize).
self.load_layout_stack_size(elem_layout, Symbol::DEV_TMP2);
// Setup the return location.
let base_offset =
self.storage_manager
.claim_stack_area_layout(self.layout_interner, dst, ret_layout);
let lowlevel_args = [
input_list,
// alignment
Symbol::DEV_TMP,
// element_width
Symbol::DEV_TMP2,
];
let lowlevel_arg_layouts = [ret_layout, Layout::U32, Layout::U64];
self.build_fn_call(
&Symbol::DEV_TMP3,
bitcode::LIST_CLONE.to_string(),
&lowlevel_args,
&lowlevel_arg_layouts,
&ret_layout,
);
self.free_symbol(&Symbol::DEV_TMP);
self.free_symbol(&Symbol::DEV_TMP2);
// Copy from list to the output record.
self.storage_manager.copy_symbol_to_stack_offset(
self.layout_interner,
&mut self.buf,
base_offset,
&Symbol::DEV_TMP3,
&ret_layout,
);
self.free_symbol(&Symbol::DEV_TMP3);
}
fn build_list_with_capacity(
@ -2770,7 +2954,11 @@ impl<
self.load_layout_alignment(list_layout, Symbol::DEV_TMP);
// Load element_width argument (usize).
self.load_layout_stack_size(*ret_layout, Symbol::DEV_TMP2);
let element_layout = match self.interner().get_repr(*ret_layout) {
LayoutRepr::Builtin(Builtin::List(e)) => e,
_ => unreachable!(),
};
self.load_layout_stack_size(element_layout, Symbol::DEV_TMP2);
// Load UpdateMode.Immutable argument (0u8)
let u8_layout = Layout::U8;
@ -3116,7 +3304,7 @@ impl<
let elem_layout = arg_layouts[1];
// List alignment argument (u32).
self.load_layout_alignment(*ret_layout, Symbol::DEV_TMP);
self.load_layout_alignment(elem_layout, Symbol::DEV_TMP);
// Have to pass the input element by pointer, so put it on the stack and load it's address.
self.storage_manager
@ -4692,14 +4880,28 @@ impl<
// move a zero into the lower 8 bytes
ASM::mov_reg64_imm64(buf, tmp_reg, 0x0);
ASM::mov_base32_reg64(buf, base_offset, tmp_reg);
ASM::mov_base32_reg64(buf, base_offset + 8, tmp_reg);
ASM::mov_base32_reg64(buf, base_offset + 8, src_reg);
ASM::mov_base32_reg64(buf, base_offset, src_reg);
self.free_symbol(&tmp);
return;
}
(U128, I128) | (I128, U128) => {
let to_offset = self.storage_manager.claim_stack_area_layout(
self.layout_interner,
*dst,
Layout::from_int_width(target),
);
let (from_offset, size) = self.storage_manager.stack_offset_and_size(src);
self.storage_manager
.copy_to_stack_offset(buf, size, from_offset, to_offset);
return;
}
_ => {}
}
@ -4823,39 +5025,51 @@ impl<
}
fn num_to_f32(&mut self, dst: &Symbol, src: &Symbol, arg_layout: &InLayout<'a>) {
let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst);
match self.layout_interner.get_repr(*arg_layout) {
LayoutRepr::Builtin(Builtin::Int(IntWidth::I32 | IntWidth::I64)) => {
let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst);
let src_reg = self.storage_manager.load_to_general_reg(&mut self.buf, src);
ASM::to_float_freg32_reg64(&mut self.buf, dst_reg, src_reg);
}
LayoutRepr::Builtin(Builtin::Float(FloatWidth::F64)) => {
let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst);
let src_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src);
ASM::to_float_freg32_freg64(&mut self.buf, dst_reg, src_reg);
}
LayoutRepr::Builtin(Builtin::Float(FloatWidth::F32)) => {
let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst);
let src_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src);
ASM::mov_freg64_freg64(&mut self.buf, dst_reg, src_reg);
}
LayoutRepr::Builtin(Builtin::Int(_)) => {
let int_width = arg_layout.to_int_width();
self.build_int_to_float_cast(dst, src, int_width, FloatWidth::F32);
}
arg => todo!("NumToFrac: layout, arg {arg:?}, ret {:?}", Layout::F32),
}
}
fn num_to_f64(&mut self, dst: &Symbol, src: &Symbol, arg_layout: &InLayout<'a>) {
let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst);
match self.layout_interner.get_repr(*arg_layout) {
LayoutRepr::Builtin(Builtin::Int(IntWidth::I32 | IntWidth::I64)) => {
let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst);
let src_reg = self.storage_manager.load_to_general_reg(&mut self.buf, src);
ASM::to_float_freg64_reg64(&mut self.buf, dst_reg, src_reg);
}
LayoutRepr::Builtin(Builtin::Float(FloatWidth::F32)) => {
let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst);
let src_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src);
ASM::to_float_freg64_freg32(&mut self.buf, dst_reg, src_reg);
}
LayoutRepr::Builtin(Builtin::Float(FloatWidth::F64)) => {
let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst);
let src_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src);
ASM::mov_freg64_freg64(&mut self.buf, dst_reg, src_reg);
}
LayoutRepr::Builtin(Builtin::Int(_)) => {
let int_width = arg_layout.to_int_width();
self.build_int_to_float_cast(dst, src, int_width, FloatWidth::F64);
}
arg => todo!("NumToFrac: layout, arg {arg:?}, ret {:?}", Layout::F64),
}
}

View file

@ -448,17 +448,26 @@ impl<
}
Stack(ReferencedPrimitive {
base_offset, size, ..
}) if base_offset % 8 == 0 && size == 8 => {
// The primitive is aligned and the data is exactly 8 bytes, treat it like regular stack.
let reg = self.get_float_reg(buf);
ASM::mov_freg64_base32(buf, reg, base_offset);
self.float_used_regs.push((reg, *sym));
self.symbol_storage_map.insert(*sym, Reg(Float(reg)));
self.free_reference(sym);
reg
}
Stack(ReferencedPrimitive { .. }) => {
todo!("loading referenced primitives")
}) => {
if base_offset % 8 == 0 && size == 8 {
// The primitive is aligned and the data is exactly 8 bytes, treat it like regular stack.
let reg = self.get_float_reg(buf);
ASM::mov_freg64_base32(buf, reg, base_offset);
self.float_used_regs.push((reg, *sym));
self.symbol_storage_map.insert(*sym, Reg(Float(reg)));
self.free_reference(sym);
reg
} else if base_offset % 4 == 0 && size == 4 {
// The primitive is aligned and the data is exactly 8 bytes, treat it like regular stack.
let reg = self.get_float_reg(buf);
ASM::mov_freg32_base32(buf, reg, base_offset);
self.float_used_regs.push((reg, *sym));
self.symbol_storage_map.insert(*sym, Reg(Float(reg)));
self.free_reference(sym);
reg
} else {
todo!("loading referenced primitives")
}
}
Stack(Complex { .. }) => {
internal_error!("Cannot load large values into float registers: {}", sym)
@ -570,12 +579,15 @@ impl<
}
Stack(ReferencedPrimitive {
base_offset, size, ..
}) if base_offset % 8 == 0 && *size == 8 => {
// The primitive is aligned and the data is exactly 8 bytes, treat it like regular stack.
ASM::mov_freg64_base32(buf, reg, *base_offset);
}
Stack(ReferencedPrimitive { .. }) => {
todo!("loading referenced primitives")
}) => {
if base_offset % 8 == 0 && *size == 8 {
// The primitive is aligned and the data is exactly 8 bytes, treat it like regular stack.
ASM::mov_freg64_base32(buf, reg, *base_offset);
} else if base_offset % 4 == 0 && *size == 4 {
ASM::mov_freg32_base32(buf, reg, *base_offset);
} else {
todo!("loading referenced primitives")
}
}
Stack(Complex { .. }) => {
internal_error!("Cannot load large values into float registers: {}", sym)
@ -682,7 +694,7 @@ impl<
}
// Loads the dst to be the later 64 bits of a list (its length).
pub fn list_len(&mut self, _buf: &mut Vec<'a, u8>, dst: &Symbol, list: &Symbol) {
pub fn list_len_u64(&mut self, _buf: &mut Vec<'a, u8>, dst: &Symbol, list: &Symbol) {
let owned_data = self.remove_allocation_for_sym(list);
self.allocation_map.insert(*list, Rc::clone(&owned_data));
self.allocation_map.insert(*dst, owned_data);
@ -697,6 +709,11 @@ impl<
);
}
/// In a 64-bit backend, this is the same as list_len_u64
pub fn list_len_usize(&mut self, buf: &mut Vec<'a, u8>, dst: &Symbol, list: &Symbol) {
self.list_len_u64(buf, dst, list)
}
/// Creates a struct on the stack, moving the data in fields into the struct.
pub fn create_struct(
&mut self,

View file

@ -1,3 +1,6 @@
#![allow(clippy::redundant_closure_call)]
//|> clippy false positive: https://github.com/rust-lang/rust-clippy/issues/1553
use crate::generic64::{storage::StorageManager, Assembler, CallConv, RegTrait};
use crate::{
pointer_layouts, single_register_floats, single_register_int_builtins,
@ -1955,6 +1958,24 @@ impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
andpd_freg64_freg64(buf, dst, src);
}
#[inline(always)]
fn abs_freg32_freg32(
buf: &mut Vec<'_, u8>,
relocs: &mut Vec<'_, Relocation>,
dst: X86_64FloatReg,
src: X86_64FloatReg,
) {
movss_freg32_rip_offset32(buf, dst, 0);
// TODO: make sure this constant only loads once instead of every call to abs
relocs.push(Relocation::LocalData {
offset: buf.len() as u64 - 4,
data: 0x7fffffffu64.to_le_bytes().to_vec(),
});
andps_freg32_freg32(buf, dst, src);
}
#[inline(always)]
fn add_reg64_reg64_imm32(
buf: &mut Vec<'_, u8>,
@ -2004,6 +2025,39 @@ impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
}
}
#[inline(always)]
fn sub_freg32_freg32_freg32(
buf: &mut Vec<'_, u8>,
dst: X86_64FloatReg,
src1: X86_64FloatReg,
src2: X86_64FloatReg,
) {
if dst == src1 {
subss_freg32_freg32(buf, dst, src2);
} else if dst == src2 {
subss_freg32_freg32(buf, dst, src1);
} else {
movss_freg32_freg32(buf, dst, src1);
subss_freg32_freg32(buf, dst, src2);
}
}
#[inline(always)]
fn sub_freg64_freg64_freg64(
buf: &mut Vec<'_, u8>,
dst: X86_64FloatReg,
src1: X86_64FloatReg,
src2: X86_64FloatReg,
) {
if dst == src1 {
subsd_freg64_freg64(buf, dst, src2);
} else if dst == src2 {
subsd_freg64_freg64(buf, dst, src1);
} else {
movsd_freg64_freg64(buf, dst, src1);
subsd_freg64_freg64(buf, dst, src2);
}
}
#[inline(always)]
fn call(buf: &mut Vec<'_, u8>, relocs: &mut Vec<'_, Relocation>, fn_name: String) {
buf.extend([0xE8, 0x00, 0x00, 0x00, 0x00]);
@ -2382,6 +2436,11 @@ impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
movsd_freg64_base64_offset32(buf, dst, X86_64GeneralReg::RBP, offset)
}
#[inline(always)]
fn mov_freg32_base32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, offset: i32) {
movss_freg32_base32_offset32(buf, dst, X86_64GeneralReg::RBP, offset)
}
#[inline(always)]
fn mov_reg_base32(
buf: &mut Vec<'_, u8>,
@ -3052,124 +3111,78 @@ fn sar_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg) {
buf.extend([rex, 0xD3, 0xC0 | (7 << 3) | dst_mod]);
}
/// `ADDSD xmm1,xmm2/m64` -> Add the low double-precision floating-point value from xmm2/mem to xmm1 and store the result in xmm1.
#[inline(always)]
fn addsd_freg64_freg64(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
fn double_binary_operation(
buf: &mut Vec<'_, u8>,
dst: X86_64FloatReg,
src: X86_64FloatReg,
float_width: FloatWidth,
op_code2: u8,
) {
let op_code1 = match float_width {
FloatWidth::F32 => 0xF3,
FloatWidth::F64 => 0xF2,
};
let dst_high = dst as u8 > 7;
let dst_mod = dst as u8 % 8;
let src_high = src as u8 > 7;
let src_mod = src as u8 % 8;
if dst_high || src_high {
buf.extend([
0xF2,
op_code1,
0x40 | ((dst_high as u8) << 2) | (src_high as u8),
0x0F,
0x58,
op_code2,
0xC0 | (dst_mod << 3) | (src_mod),
])
} else {
buf.extend([0xF2, 0x0F, 0x58, 0xC0 | (dst_mod << 3) | (src_mod)])
buf.extend([op_code1, 0x0F, op_code2, 0xC0 | (dst_mod << 3) | (src_mod)])
}
}
/// `ADDSD xmm1,xmm2/m64` -> Add the low double-precision floating-point value from xmm2/mem to xmm1 and store the result in xmm1.
#[inline(always)]
fn addsd_freg64_freg64(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
double_binary_operation(buf, dst, src, FloatWidth::F64, 0x58)
}
/// `ADDSS xmm1,xmm2/m64` -> Add the low single-precision floating-point value from xmm2/mem to xmm1 and store the result in xmm1.
#[inline(always)]
fn addss_freg32_freg32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
let dst_high = dst as u8 > 7;
let dst_mod = dst as u8 % 8;
let src_high = src as u8 > 7;
let src_mod = src as u8 % 8;
if dst_high || src_high {
buf.extend([
0xF3,
0x40 | ((dst_high as u8) << 2) | (src_high as u8),
0x0F,
0x58,
0xC0 | (dst_mod << 3) | (src_mod),
])
} else {
buf.extend([0xF3, 0x0F, 0x58, 0xC0 | (dst_mod << 3) | (src_mod)])
}
double_binary_operation(buf, dst, src, FloatWidth::F32, 0x58)
}
/// `SUBSD xmm1,xmm2/m64` -> Sub the low double-precision floating-point value from xmm2/mem to xmm1 and store the result in xmm1.
#[inline(always)]
fn subsd_freg64_freg64(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
double_binary_operation(buf, dst, src, FloatWidth::F64, 0x5C)
}
/// `SUBSS xmm1,xmm2/m64` -> Sub the low single-precision floating-point value from xmm2/mem to xmm1 and store the result in xmm1.
#[inline(always)]
fn subss_freg32_freg32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
double_binary_operation(buf, dst, src, FloatWidth::F32, 0x5C)
}
/// `MULSD xmm1,xmm2/m64` -> Multiply the low double-precision floating-point value from xmm2/mem to xmm1 and store the result in xmm1.
#[inline(always)]
fn mulsd_freg64_freg64(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
let dst_high = dst as u8 > 7;
let dst_mod = dst as u8 % 8;
let src_high = src as u8 > 7;
let src_mod = src as u8 % 8;
if dst_high || src_high {
buf.extend([
0xF2,
0x40 | ((dst_high as u8) << 2) | (src_high as u8),
0x0F,
0x59,
0xC0 | (dst_mod << 3) | (src_mod),
])
} else {
buf.extend([0xF2, 0x0F, 0x59, 0xC0 | (dst_mod << 3) | (src_mod)])
}
double_binary_operation(buf, dst, src, FloatWidth::F64, 0x59)
}
#[inline(always)]
fn mulss_freg32_freg32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
double_binary_operation(buf, dst, src, FloatWidth::F32, 0x59)
}
/// `DIVSS xmm1,xmm2/m64` -> Divide the low single-precision floating-point value from xmm2/mem to xmm1 and store the result in xmm1.
#[inline(always)]
fn divss_freg32_freg32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
let dst_high = dst as u8 > 7;
let dst_mod = dst as u8 % 8;
let src_high = src as u8 > 7;
let src_mod = src as u8 % 8;
if dst_high || src_high {
buf.extend([
0xF3,
0x40 | ((dst_high as u8) << 2) | (src_high as u8),
0x0F,
0x5E,
0xC0 | (dst_mod << 3) | (src_mod),
])
} else {
buf.extend([0xF3, 0x0F, 0x5E, 0xC0 | (dst_mod << 3) | (src_mod)])
}
double_binary_operation(buf, dst, src, FloatWidth::F32, 0x5E)
}
/// `DIVSD xmm1,xmm2/m64` -> Divide the low double-precision floating-point value from xmm2/mem to xmm1 and store the result in xmm1.
#[inline(always)]
fn divsd_freg64_freg64(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
let dst_high = dst as u8 > 7;
let dst_mod = dst as u8 % 8;
let src_high = src as u8 > 7;
let src_mod = src as u8 % 8;
if dst_high || src_high {
buf.extend([
0xF2,
0x40 | ((dst_high as u8) << 2) | (src_high as u8),
0x0F,
0x5E,
0xC0 | (dst_mod << 3) | (src_mod),
])
} else {
buf.extend([0xF2, 0x0F, 0x5E, 0xC0 | (dst_mod << 3) | (src_mod)])
}
}
/// `ADDSS xmm1,xmm2/m64` -> Add the low single-precision floating-point value from xmm2/mem to xmm1 and store the result in xmm1.
#[inline(always)]
fn mulss_freg32_freg32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
let dst_high = dst as u8 > 7;
let dst_mod = dst as u8 % 8;
let src_high = src as u8 > 7;
let src_mod = src as u8 % 8;
if dst_high || src_high {
buf.extend([
0xF3,
0x40 | ((dst_high as u8) << 2) | (src_high as u8),
0x0F,
0x59,
0xC0 | (dst_mod << 3) | (src_mod),
])
} else {
buf.extend([0xF3, 0x0F, 0x59, 0xC0 | (dst_mod << 3) | (src_mod)])
}
double_binary_operation(buf, dst, src, FloatWidth::F64, 0x5E)
}
#[inline(always)]
@ -3192,6 +3205,25 @@ fn andpd_freg64_freg64(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64Fl
}
}
#[inline(always)]
fn andps_freg32_freg32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
let dst_high = dst as u8 > 7;
let dst_mod = dst as u8 % 8;
let src_high = src as u8 > 7;
let src_mod = src as u8 % 8;
if dst_high || src_high {
buf.extend([
0x40 | ((dst_high as u8) << 2) | (src_high as u8),
0x0F,
0x54,
0xC0 | (dst_mod << 3) | (src_mod),
])
} else {
buf.extend([0x0F, 0x54, 0xC0 | (dst_mod << 3) | (src_mod)])
}
}
/// r/m64 AND imm8 (sign-extended).
#[inline(always)]
fn and_reg64_imm8(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, imm: i8) {
@ -4496,6 +4528,16 @@ mod tests {
);
}
#[test]
fn test_andps_freg32_freg32() {
disassembler_test!(
andps_freg32_freg32,
|reg1, reg2| format!("andps {reg1}, {reg2}"),
ALL_FLOAT_REGS,
ALL_FLOAT_REGS
);
}
#[test]
fn test_and_reg64_reg64() {
disassembler_test!(

View file

@ -358,27 +358,21 @@ trait Backend<'a> {
where
I: Iterator<Item = InLayout<'b>>,
{
use std::fmt::Write;
use std::hash::{BuildHasher, Hash, Hasher};
let symbol = name.name();
let mut buf = String::with_capacity(1024);
// NOTE: due to randomness, this will not be consistent between runs
let mut state = roc_collections::all::BuildHasher::default().build_hasher();
for a in arguments {
write!(buf, "{:?}", self.interner().dbg_stable(a)).expect("capacity");
a.hash(&mut state);
}
// lambda set should not matter; it should already be added as an argument
// but the niche of the lambda name may be the only thing differentiating two different
// implementations of a function with the same symbol
write!(buf, "{:?}", name.niche().dbg_stable(self.interner())).expect("capacity");
write!(buf, "{:?}", self.interner().dbg_stable(result)).expect("capacity");
// NOTE: due to randomness, this will not be consistent between runs
let mut state = roc_collections::all::BuildHasher::default().build_hasher();
buf.hash(&mut state);
name.niche().hash(&mut state);
result.hash(&mut state);
let interns = self.interns();
let ident_string = symbol.as_str(interns);
@ -1010,7 +1004,7 @@ trait Backend<'a> {
arg_layouts[0], *ret_layout,
"NumAdd: expected to have the same argument and return layout"
);
self.build_num_add(sym, &args[0], &args[1], ret_layout)
self.build_num_add_wrap(sym, &args[0], &args[1], ret_layout)
}
LowLevel::NumAddChecked => {
self.build_num_add_checked(sym, &args[0], &args[1], &arg_layouts[0], ret_layout)
@ -1080,13 +1074,80 @@ trait Backend<'a> {
);
self.build_num_neg(sym, &args[0], ret_layout)
}
LowLevel::NumPowInt => self.build_fn_call(
sym,
bitcode::NUM_POW_INT[IntWidth::I64].to_string(),
args,
arg_layouts,
ret_layout,
),
LowLevel::NumPowInt => {
let repr = self.interner().get_repr(arg_layouts[0]);
let LayoutRepr::Builtin(Builtin::Int(int_width)) = repr else {
unreachable!("invalid layout for NumPowInt")
};
self.build_fn_call(
sym,
bitcode::NUM_POW_INT[int_width].to_string(),
args,
arg_layouts,
ret_layout,
)
}
LowLevel::NumPow => {
let intrinsic = match self.interner().get_repr(arg_layouts[0]) {
LayoutRepr::Builtin(Builtin::Float(float_width)) => {
&bitcode::NUM_POW[float_width]
}
LayoutRepr::DEC => bitcode::DEC_POW,
_ => unreachable!("invalid layout for NumPow"),
};
self.build_fn_call(sym, intrinsic.to_string(), args, arg_layouts, ret_layout)
}
LowLevel::NumRound => {
let repr = self.interner().get_repr(*ret_layout);
let LayoutRepr::Builtin(Builtin::Int(int_width)) = repr else {
unreachable!("invalid return layout for NumRound")
};
let intrinsic = match arg_layouts[0] {
Layout::F32 => &bitcode::NUM_ROUND_F32[int_width],
Layout::F64 => &bitcode::NUM_ROUND_F64[int_width],
Layout::DEC => &bitcode::DEC_ROUND[int_width],
_ => unreachable!("invalid layout for NumRound"),
};
self.build_fn_call(sym, intrinsic.to_string(), args, arg_layouts, ret_layout)
}
LowLevel::NumFloor => {
let repr = self.interner().get_repr(*ret_layout);
let LayoutRepr::Builtin(Builtin::Int(int_width)) = repr else {
unreachable!("invalid return layout for NumFloor")
};
let intrinsic = match arg_layouts[0] {
Layout::F32 => &bitcode::NUM_FLOOR_F32[int_width],
Layout::F64 => &bitcode::NUM_FLOOR_F64[int_width],
Layout::DEC => &bitcode::DEC_FLOOR[int_width],
_ => unreachable!("invalid layout for NumFloor"),
};
self.build_fn_call(sym, intrinsic.to_string(), args, arg_layouts, ret_layout)
}
LowLevel::NumCeiling => {
let repr = self.interner().get_repr(*ret_layout);
let LayoutRepr::Builtin(Builtin::Int(int_width)) = repr else {
unreachable!("invalid return layout for NumCeiling")
};
let intrinsic = match arg_layouts[0] {
Layout::F32 => &bitcode::NUM_CEILING_F32[int_width],
Layout::F64 => &bitcode::NUM_CEILING_F64[int_width],
Layout::DEC => &bitcode::DEC_CEILING[int_width],
_ => unreachable!("invalid layout for NumCeiling"),
};
self.build_fn_call(sym, intrinsic.to_string(), args, arg_layouts, ret_layout)
}
LowLevel::NumSub => {
debug_assert_eq!(
2,
@ -1393,20 +1454,51 @@ trait Backend<'a> {
self.build_num_sqrt(*sym, args[0], float_width);
}
LowLevel::NumRound => self.build_fn_call(
sym,
bitcode::NUM_ROUND_F64[IntWidth::I64].to_string(),
args,
arg_layouts,
ret_layout,
),
LowLevel::ListLen => {
LowLevel::NumSin => {
let intrinsic = match arg_layouts[0] {
Layout::F64 => &bitcode::NUM_SIN[FloatWidth::F64],
Layout::F32 => &bitcode::NUM_SIN[FloatWidth::F32],
Layout::DEC => bitcode::DEC_SIN,
_ => unreachable!("invalid layout for sin"),
};
self.build_fn_call(sym, intrinsic.to_string(), args, arg_layouts, ret_layout)
}
LowLevel::NumCos => {
let intrinsic = match arg_layouts[0] {
Layout::F64 => &bitcode::NUM_COS[FloatWidth::F64],
Layout::F32 => &bitcode::NUM_COS[FloatWidth::F32],
Layout::DEC => bitcode::DEC_COS,
_ => unreachable!("invalid layout for cos"),
};
self.build_fn_call(sym, intrinsic.to_string(), args, arg_layouts, ret_layout)
}
LowLevel::NumTan => {
let intrinsic = match arg_layouts[0] {
Layout::F64 => &bitcode::NUM_TAN[FloatWidth::F64],
Layout::F32 => &bitcode::NUM_TAN[FloatWidth::F32],
Layout::DEC => bitcode::DEC_TAN,
_ => unreachable!("invalid layout for tan"),
};
self.build_fn_call(sym, intrinsic.to_string(), args, arg_layouts, ret_layout)
}
LowLevel::ListLenU64 => {
debug_assert_eq!(
1,
args.len(),
"ListLen: expected to have exactly one argument"
"ListLenU64: expected to have exactly one argument"
);
self.build_list_len(sym, &args[0])
self.build_list_len_u64(sym, &args[0])
}
LowLevel::ListLenUsize => {
debug_assert_eq!(
1,
args.len(),
"ListLenUsize: expected to have exactly one argument"
);
self.build_list_len_usize(sym, &args[0])
}
LowLevel::ListWithCapacity => {
debug_assert_eq!(
@ -1417,6 +1509,15 @@ trait Backend<'a> {
let elem_layout = list_element_layout!(self.interner(), *ret_layout);
self.build_list_with_capacity(sym, args[0], arg_layouts[0], elem_layout, ret_layout)
}
LowLevel::ListClone => {
debug_assert_eq!(
1,
args.len(),
"ListClone: expected to have exactly one argument"
);
let elem_layout = list_element_layout!(self.interner(), *ret_layout);
self.build_list_clone(*sym, args[0], elem_layout, *ret_layout)
}
LowLevel::ListReserve => {
debug_assert_eq!(
2,
@ -1494,20 +1595,6 @@ trait Backend<'a> {
arg_layouts,
ret_layout,
),
LowLevel::StrStartsWithScalar => self.build_fn_call(
sym,
bitcode::STR_STARTS_WITH_SCALAR.to_string(),
args,
arg_layouts,
ret_layout,
),
LowLevel::StrAppendScalar => self.build_fn_call(
sym,
bitcode::STR_APPEND_SCALAR.to_string(),
args,
arg_layouts,
ret_layout,
),
LowLevel::StrEndsWith => self.build_fn_call(
sym,
bitcode::STR_ENDS_WITH.to_string(),
@ -1515,13 +1602,6 @@ trait Backend<'a> {
arg_layouts,
ret_layout,
),
LowLevel::StrCountGraphemes => self.build_fn_call(
sym,
bitcode::STR_COUNT_GRAPEHEME_CLUSTERS.to_string(),
args,
arg_layouts,
ret_layout,
),
LowLevel::StrSubstringUnsafe => self.build_fn_call(
sym,
bitcode::STR_SUBSTRING_UNSAFE.to_string(),
@ -1543,15 +1623,17 @@ trait Backend<'a> {
arg_layouts,
ret_layout,
),
LowLevel::StrFromUtf8Range => {
LowLevel::StrFromUtf8 => {
let update_mode = self.debug_symbol("update_mode");
// In dev builds, always use UpdateMode::Immutable
self.load_literal_i8(&update_mode, UpdateMode::Immutable as i8);
self.build_fn_call(
sym,
bitcode::STR_FROM_UTF8_RANGE.to_string(),
&[args[0], args[1], args[2], update_mode],
&[arg_layouts[0], arg_layouts[1], arg_layouts[2], Layout::U8],
bitcode::STR_FROM_UTF8.to_string(),
&[args[0], update_mode],
&[arg_layouts[0], Layout::U8],
ret_layout,
)
}
@ -1597,13 +1679,6 @@ trait Backend<'a> {
arg_layouts,
ret_layout,
),
LowLevel::StrToScalars => self.build_fn_call(
sym,
bitcode::STR_TO_SCALARS.to_string(),
args,
arg_layouts,
ret_layout,
),
LowLevel::StrGetUnsafe => self.build_fn_call(
sym,
bitcode::STR_GET_UNSAFE.to_string(),
@ -1611,13 +1686,6 @@ trait Backend<'a> {
arg_layouts,
ret_layout,
),
LowLevel::StrGetScalarUnsafe => self.build_fn_call(
sym,
bitcode::STR_GET_SCALAR_UNSAFE.to_string(),
args,
arg_layouts,
ret_layout,
),
LowLevel::StrToNum => {
let number_layout = match self.interner().get_repr(*ret_layout) {
LayoutRepr::Struct(field_layouts) => field_layouts[0], // TODO: why is it sometimes a struct?
@ -1792,8 +1860,8 @@ trait Backend<'a> {
// list: RocList,
// alignment: u32,
// element_width: usize,
// start: usize,
// len: usize,
// start: u64,
// len: u64,
// dec: Dec,
let list = args[0];
@ -1819,8 +1887,8 @@ trait Backend<'a> {
arg_layouts[0],
Layout::U32,
layout_usize,
arg_layouts[1],
arg_layouts[2],
Layout::U64,
Layout::U64,
layout_usize,
];
@ -1843,8 +1911,8 @@ trait Backend<'a> {
// list: RocList,
// alignment: u32,
// element_width: usize,
// index_1: usize,
// index_2: usize,
// index_1: u64,
// index_2: u64,
// update_mode: UpdateMode,
self.build_fn_call(
@ -1862,8 +1930,8 @@ trait Backend<'a> {
list_layout,
Layout::U32,
layout_usize,
layout_usize,
layout_usize,
Layout::U64,
Layout::U64,
Layout::U8,
],
ret_layout,
@ -1916,7 +1984,7 @@ trait Backend<'a> {
// list: RocList,
// alignment: u32,
// element_width: usize,
// drop_index: usize,
// drop_index: u64,
// dec: Dec,
self.build_fn_call(
@ -1933,7 +2001,7 @@ trait Backend<'a> {
list_layout,
Layout::U32,
layout_usize,
layout_usize,
Layout::U64,
layout_usize,
],
ret_layout,
@ -1944,6 +2012,23 @@ trait Backend<'a> {
self.build_num_cmp(sym, &args[0], &args[1], &arg_layouts[0]);
}
LowLevel::NumToFloatCast => {
let float_width = match *ret_layout {
Layout::F64 => FloatWidth::F64,
Layout::F32 => FloatWidth::F32,
_ => unreachable!("invalid return layout for NumToFloatCast"),
};
match arg_layouts[0].try_to_int_width() {
Some(int_width) => {
self.build_int_to_float_cast(sym, &args[0], int_width, float_width);
}
None => {
self.build_num_to_frac(sym, &args[0], &arg_layouts[0], ret_layout);
}
}
}
x => todo!("low level, {:?}", x),
}
}
@ -1970,12 +2055,15 @@ trait Backend<'a> {
"NumIsZero: expected to have return layout of type Bool"
);
let literal = match self.interner().get_repr(arg_layouts[0]) {
LayoutRepr::Builtin(Builtin::Int(_)) => Literal::Int(0i128.to_ne_bytes()),
LayoutRepr::Builtin(Builtin::Float(_)) => Literal::Float(0.0),
LayoutRepr::DEC => Literal::Decimal([0; 16]),
_ => unreachable!("invalid layout for sin"),
};
self.load_literal_symbols(args);
self.load_literal(
&Symbol::DEV_TMP,
&arg_layouts[0],
&Literal::Int(0i128.to_ne_bytes()),
);
self.load_literal(&Symbol::DEV_TMP, &arg_layouts[0], &literal);
self.build_eq(sym, &args[0], &Symbol::DEV_TMP, &arg_layouts[0]);
self.free_symbol(&Symbol::DEV_TMP)
}
@ -2016,18 +2104,6 @@ trait Backend<'a> {
self.load_literal(sym, BOOL_LAYOUT, LITERAL);
}
}
Symbol::STR_IS_VALID_SCALAR => {
// just call the function
let fn_name = self.lambda_name_to_string(
func_name,
arg_layouts.iter().copied(),
None,
*ret_layout,
);
// Now that the arguments are needed, load them if they are literals.
self.load_literal_symbols(args);
self.build_fn_call(sym, fn_name, args, arg_layouts, ret_layout)
}
_other => {
// just call the function
let fn_name = self.lambda_name_to_string(
@ -2069,6 +2145,14 @@ trait Backend<'a> {
target: IntWidth,
);
fn build_int_to_float_cast(
&mut self,
dst: &Symbol,
src: &Symbol,
int_width: IntWidth,
float_width: FloatWidth,
);
/// build_num_abs stores the absolute value of src into dst.
fn build_num_abs(&mut self, dst: &Symbol, src: &Symbol, layout: &InLayout<'a>);
@ -2094,6 +2178,14 @@ trait Backend<'a> {
return_layout: &InLayout<'a>,
);
fn build_num_add_wrap(
&mut self,
dst: &Symbol,
src1: &Symbol,
src2: &Symbol,
layout: &InLayout<'a>,
);
/// build_num_sub_checked stores the sum of src1 and src2 into dst.
fn build_num_sub_checked(
&mut self,
@ -2290,8 +2382,11 @@ trait Backend<'a> {
/// build_sqrt stores the result of `sqrt(src)` into dst.
fn build_num_sqrt(&mut self, dst: Symbol, src: Symbol, float_width: FloatWidth);
/// build_list_len returns the length of a list.
fn build_list_len(&mut self, dst: &Symbol, list: &Symbol);
/// build_list_len_usize returns the length of a list as a usize. This is for internal use only.
fn build_list_len_usize(&mut self, dst: &Symbol, list: &Symbol);
/// build_list_len_u64 returns the length of a list and casts it from usize to u64. This is for the public List.len.
fn build_list_len_u64(&mut self, dst: &Symbol, list: &Symbol);
/// generate a call to a higher-order lowlevel
fn build_higher_order_lowlevel(
@ -2304,6 +2399,14 @@ trait Backend<'a> {
fn build_indirect_inc(&mut self, layout: InLayout<'a>) -> Symbol;
fn build_indirect_dec(&mut self, layout: InLayout<'a>) -> Symbol;
fn build_list_clone(
&mut self,
dst: Symbol,
input_list: Symbol,
elem_layout: InLayout<'a>,
ret_layout: InLayout<'a>,
);
/// build_list_with_capacity creates and returns a list with the given capacity.
fn build_list_with_capacity(
&mut self,

View file

@ -29,6 +29,28 @@ pub fn build_module<'a, 'r>(
layout_interner: &'r mut STLayoutInterner<'a>,
target: &Triple,
procedures: MutMap<(symbol::Symbol, ProcLayout<'a>), Proc<'a>>,
) -> Object<'a> {
let module_object = build_module_help(env, interns, layout_interner, target, procedures);
if std::env::var("ROC_DEV_WRITE_OBJ").is_ok() {
let module_out = module_object
.write()
.expect("failed to build output object");
let file_path = std::env::temp_dir().join("app.o");
println!("gen-test object file written to {}", file_path.display());
std::fs::write(&file_path, module_out).expect("failed to write object to file");
}
module_object
}
fn build_module_help<'a, 'r>(
env: &'r Env<'a>,
interns: &'r mut Interns,
layout_interner: &'r mut STLayoutInterner<'a>,
target: &Triple,
procedures: MutMap<(symbol::Symbol, ProcLayout<'a>), Proc<'a>>,
) -> Object<'a> {
match target {
Triple {

View file

@ -949,6 +949,12 @@ impl<'a, 'ctx, 'env> Env<'a, 'ctx, 'env> {
}
pub fn new_debug_info(module: &Module<'ctx>) -> (DebugInfoBuilder<'ctx>, DICompileUnit<'ctx>) {
let debug_metadata_version = module.get_context().i32_type().const_int(3, false);
module.add_basic_value_flag(
"Debug Info Version",
inkwell::module::FlagBehavior::Warning,
debug_metadata_version,
);
module.create_debug_info_builder(
true,
/* language */ inkwell::debug_info::DWARFSourceLanguage::C,
@ -1065,17 +1071,19 @@ pub fn module_from_builtins<'ctx>(
// Anything not depended on by a `roc_builtin.` function could already by DCE'd theoretically.
// That said, this workaround is good enough and fixes compilations times.
// Also, must_keep is the functions we depend on that would normally be provide by libc.
// Also, must_keep is the functions we depend on that would normally be provide by libc or compiler-rt.
// They are magically linked to by llvm builtins, so we must specify that they can't be DCE'd.
let must_keep = [
// Windows special required when floats are used
"_fltused",
// From libc
"floorf",
"memcpy",
"memset",
// I have no idea why this function is special.
// Without it, some tests hang on M1 mac outside of nix.
// From compiler-rt
"__divti3",
"__modti3",
"__muloti4",
// fixes `Undefined Symbol in relocation`
"__udivti3",
// Roc special functions
"__roc_force_longjmp",
@ -4897,10 +4905,8 @@ fn expose_function_to_host_help_c_abi_v2<'a, 'ctx>(
Attribute::get_named_enum_kind_id("byval"),
c_abi_type.as_any_type_enum(),
);
let nonnull = context.create_type_attribute(
Attribute::get_named_enum_kind_id("nonnull"),
c_abi_type.as_any_type_enum(),
);
let nonnull = context
.create_enum_attribute(Attribute::get_named_enum_kind_id("nonnull"), 0);
// C return pointer goes at the beginning of params, and we must skip it if it exists.
let returns_pointer = matches!(cc_return, CCReturn::ByPointer);
let param_index = i as u32 + returns_pointer as u32;
@ -5066,6 +5072,11 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx>(
}
pub fn get_sjlj_buffer<'ctx>(env: &Env<'_, 'ctx, '_>) -> PointerValue<'ctx> {
let word_type = match env.target_info.ptr_width() {
PtrWidth::Bytes4 => env.context.i32_type(),
PtrWidth::Bytes8 => env.context.i64_type(),
};
// The size of jump_buf is target-dependent.
// - AArch64 needs 3 machine-sized words
// - LLVM says the following about the SJLJ intrinsic:
@ -5077,11 +5088,15 @@ pub fn get_sjlj_buffer<'ctx>(env: &Env<'_, 'ctx, '_>) -> PointerValue<'ctx> {
// The following three words are available for use in a target-specific manner.
//
// So, let's create a 5-word buffer.
let word_type = match env.target_info.ptr_width() {
PtrWidth::Bytes4 => env.context.i32_type(),
PtrWidth::Bytes8 => env.context.i64_type(),
let size = if env.target_info.operating_system == roc_target::OperatingSystem::Windows {
// Due to https://github.com/llvm/llvm-project/issues/72908
// on windows, we store the register contents into this buffer directly!
30
} else {
5
};
let type_ = word_type.array_type(5);
let type_ = word_type.array_type(size);
let global = match env.module.get_global("roc_sjlj_buffer") {
Some(global) => global,
@ -5099,9 +5114,12 @@ pub fn get_sjlj_buffer<'ctx>(env: &Env<'_, 'ctx, '_>) -> PointerValue<'ctx> {
pub fn build_setjmp_call<'ctx>(env: &Env<'_, 'ctx, '_>) -> BasicValueEnum<'ctx> {
let jmp_buf = get_sjlj_buffer(env);
if cfg!(target_arch = "aarch64") {
if env.target_info.architecture == roc_target::Architecture::Aarch64 {
// Due to https://github.com/roc-lang/roc/issues/2965, we use a setjmp we linked in from Zig
call_bitcode_fn(env, &[jmp_buf.into()], bitcode::UTILS_SETJMP)
} else if env.target_info.operating_system == roc_target::OperatingSystem::Windows {
// Due to https://github.com/llvm/llvm-project/issues/72908, we use a setjmp defined as asm in Zig
call_bitcode_fn(env, &[jmp_buf.into()], bitcode::UTILS_WINDOWS_SETJMP)
} else {
// Anywhere else, use the LLVM intrinsic.
// https://llvm.org/docs/ExceptionHandling.html#llvm-eh-sjlj-setjmp
@ -6735,9 +6753,7 @@ pub fn to_cc_return<'a>(
) -> CCReturn {
let return_size = layout_interner.stack_size(layout);
let pass_result_by_pointer = match env.target_info.operating_system {
roc_target::OperatingSystem::Windows => {
return_size >= 2 * env.target_info.ptr_width() as u32
}
roc_target::OperatingSystem::Windows => return_size > env.target_info.ptr_width() as u32,
roc_target::OperatingSystem::Unix => return_size > 2 * env.target_info.ptr_width() as u32,
roc_target::OperatingSystem::Wasi => return_size > 2 * env.target_info.ptr_width() as u32,
};

View file

@ -143,6 +143,8 @@ pub(crate) fn list_get_unsafe<'a, 'ctx>(
layout_interner,
layout_interner.get_repr(element_layout),
);
// listGetUnsafe takes a U64, but we need to convert that to usize for index calculation.
let elem_index = builder.new_build_int_cast(elem_index, env.ptr_int(), "u64_to_usize");
let ptr_type = elem_type.ptr_type(AddressSpace::default());
// Load the pointer to the array data
let array_data_ptr = load_list_ptr(builder, wrapper_struct, ptr_type);
@ -167,7 +169,7 @@ pub(crate) fn list_get_unsafe<'a, 'ctx>(
)
}
/// List.reserve : List elem, Nat -> List elem
/// List.reserve : List elem, U64 -> List elem
pub(crate) fn list_reserve<'a, 'ctx>(
env: &Env<'a, 'ctx, '_>,
layout_interner: &STLayoutInterner<'a>,
@ -248,7 +250,7 @@ pub(crate) fn list_prepend<'a, 'ctx>(
)
}
/// List.swap : List elem, Nat, Nat -> List elem
/// List.swap : List elem, U64, U64 -> List elem
pub(crate) fn list_swap<'a, 'ctx>(
env: &Env<'a, 'ctx, '_>,
layout_interner: &STLayoutInterner<'a>,
@ -272,7 +274,7 @@ pub(crate) fn list_swap<'a, 'ctx>(
)
}
/// List.sublist : List elem, { start : Nat, len : Nat } -> List elem
/// List.sublist : List elem, { start : U64, len : U64 } -> List elem
pub(crate) fn list_sublist<'a, 'ctx>(
env: &Env<'a, 'ctx, '_>,
layout_interner: &STLayoutInterner<'a>,
@ -297,7 +299,7 @@ pub(crate) fn list_sublist<'a, 'ctx>(
)
}
/// List.dropAt : List elem, Nat -> List elem
/// List.dropAt : List elem, U64 -> List elem
pub(crate) fn list_drop_at<'a, 'ctx>(
env: &Env<'a, 'ctx, '_>,
layout_interner: &STLayoutInterner<'a>,
@ -320,7 +322,7 @@ pub(crate) fn list_drop_at<'a, 'ctx>(
)
}
/// List.replace_unsafe : List elem, Nat, elem -> { list: List elem, value: elem }
/// List.replace_unsafe : List elem, U64, elem -> { list: List elem, value: elem }
pub(crate) fn list_replace_unsafe<'a, 'ctx>(
env: &Env<'a, 'ctx, '_>,
layout_interner: &STLayoutInterner<'a>,
@ -423,8 +425,8 @@ fn bounds_check_comparison<'ctx>(
builder.new_build_int_compare(IntPredicate::ULT, elem_index, len, "bounds_check")
}
/// List.len : List * -> Nat
pub(crate) fn list_len<'ctx>(
/// List.len : List * -> usize (return value will be cast to U64 in user-facing API)
pub(crate) fn list_len_usize<'ctx>(
builder: &Builder<'ctx>,
wrapper_struct: StructValue<'ctx>,
) -> IntValue<'ctx> {

View file

@ -13,12 +13,11 @@ pub(crate) fn decode_from_utf8_result<'a, 'ctx>(
layout_interner: &STLayoutInterner<'a>,
pointer: PointerValue<'ctx>,
) -> BasicValueEnum<'ctx> {
let layout = LayoutRepr::Struct(env.arena.alloc([
Layout::usize(env.target_info),
Layout::STR,
Layout::BOOL,
Layout::U8,
]));
let layout =
LayoutRepr::Struct(
env.arena
.alloc([Layout::U64, Layout::STR, Layout::BOOL, Layout::U8]),
);
load_roc_value(
env,

View file

@ -1,5 +1,5 @@
use crate::llvm::build::{get_tag_id, tag_pointer_clear_tag_id, Env, FAST_CALL_CONV};
use crate::llvm::build_list::{list_len, load_list_ptr};
use crate::llvm::build_list::{list_len_usize, load_list_ptr};
use crate::llvm::build_str::str_equal;
use crate::llvm::convert::basic_type_from_layout;
use bumpalo::collections::Vec;
@ -510,8 +510,8 @@ fn build_list_eq_help<'a, 'ctx>(
// first, check whether the length is equal
let len1 = list_len(env.builder, list1);
let len2 = list_len(env.builder, list2);
let len1 = list_len_usize(env.builder, list1);
let len2 = list_len_usize(env.builder, list2);
let length_equal: IntValue =
env.builder

View file

@ -978,22 +978,18 @@ fn build_clone_builtin<'a, 'ctx>(
cursors.extra_offset
}
Builtin::Str => {
//
call_str_bitcode_fn(
env,
&[value],
&[
ptr.into(),
cursors.offset.into(),
cursors.extra_offset.into(),
],
crate::llvm::bitcode::BitcodeReturns::Basic,
bitcode::STR_CLONE_TO,
)
.into_int_value()
}
Builtin::Str => call_str_bitcode_fn(
env,
&[value],
&[
ptr.into(),
cursors.offset.into(),
cursors.extra_offset.into(),
],
crate::llvm::bitcode::BitcodeReturns::Basic,
bitcode::STR_CLONE_TO,
)
.into_int_value(),
Builtin::List(elem) => {
let bd = env.builder;

View file

@ -315,11 +315,18 @@ pub fn add_sjlj_roc_panic(env: &Env<'_, '_, '_>) {
pub fn build_longjmp_call(env: &Env) {
let jmp_buf = get_sjlj_buffer(env);
if cfg!(target_arch = "aarch64") {
// Call the Zig-linked longjmp: `void longjmp(i32*, i32)`
if env.target_info.architecture == roc_target::Architecture::Aarch64 {
// Due to https://github.com/roc-lang/roc/issues/2965, we use a setjmp we linked in from Zig
let tag = env.context.i32_type().const_int(1, false);
let _call =
call_void_bitcode_fn(env, &[jmp_buf.into(), tag.into()], bitcode::UTILS_LONGJMP);
} else if env.target_info.operating_system == roc_target::OperatingSystem::Windows {
let tag = env.context.i32_type().const_int(1, false);
let _call = call_void_bitcode_fn(
env,
&[jmp_buf.into(), tag.into()],
bitcode::UTILS_WINDOWS_LONGJMP,
);
} else {
// Call the LLVM-intrinsic longjmp: `void @llvm.eh.sjlj.longjmp(i8* %setjmp_buf)`
let jmp_buf_i8p = env.builder.new_build_pointer_cast(

View file

@ -34,10 +34,10 @@ use crate::llvm::{
BuilderExt, FuncBorrowSpec, RocReturn,
},
build_list::{
list_append_unsafe, list_concat, list_drop_at, list_get_unsafe, list_len, list_map,
list_map2, list_map3, list_map4, list_prepend, list_release_excess_capacity,
list_replace_unsafe, list_reserve, list_sort_with, list_sublist, list_swap,
list_symbol_to_c_abi, list_with_capacity, pass_update_mode,
layout_width, list_append_unsafe, list_concat, list_drop_at, list_get_unsafe,
list_len_usize, list_map, list_map2, list_map3, list_map4, list_prepend,
list_release_excess_capacity, list_replace_unsafe, list_reserve, list_sort_with,
list_sublist, list_swap, list_symbol_to_c_abi, list_with_capacity, pass_update_mode,
},
compare::{generic_eq, generic_neq},
convert::{
@ -153,18 +153,6 @@ pub(crate) fn run_low_level<'a, 'ctx>(
}
}
}
StrToScalars => {
// Str.toScalars : Str -> List U32
arguments!(string);
call_str_bitcode_fn(
env,
&[string],
&[],
BitcodeReturns::List,
bitcode::STR_TO_SCALARS,
)
}
StrStartsWith => {
// Str.startsWith : Str, Str -> Bool
arguments!(string, prefix);
@ -177,18 +165,6 @@ pub(crate) fn run_low_level<'a, 'ctx>(
bitcode::STR_STARTS_WITH,
)
}
StrStartsWithScalar => {
// Str.startsWithScalar : Str, U32 -> Bool
arguments!(string, prefix);
call_str_bitcode_fn(
env,
&[string],
&[prefix],
BitcodeReturns::Basic,
bitcode::STR_STARTS_WITH_SCALAR,
)
}
StrEndsWith => {
// Str.startsWith : Str, Str -> Bool
arguments!(string, prefix);
@ -432,7 +408,7 @@ pub(crate) fn run_low_level<'a, 'ctx>(
&bitcode::STR_FROM_FLOAT[float_width],
)
}
StrFromUtf8Range => {
StrFromUtf8 => {
let result_type = env.module.get_struct_type("str.FromUtf8Result").unwrap();
let result_ptr = env
.builder
@ -441,7 +417,7 @@ pub(crate) fn run_low_level<'a, 'ctx>(
use roc_target::Architecture::*;
match env.target_info.architecture {
Aarch32 | X86_32 => {
arguments!(list, start, count);
arguments!(list);
let (a, b) = pass_list_or_string_to_zig_32bit(env, list.into_struct_value());
call_void_bitcode_fn(
@ -450,15 +426,13 @@ pub(crate) fn run_low_level<'a, 'ctx>(
result_ptr.into(),
a.into(),
b.into(),
start,
count,
pass_update_mode(env, update_mode),
],
bitcode::STR_FROM_UTF8_RANGE,
bitcode::STR_FROM_UTF8,
);
}
Aarch64 | X86_64 | Wasm32 => {
arguments!(_list, start, count);
arguments!(_list);
// we use the symbol here instead
let list = args[0];
@ -468,11 +442,9 @@ pub(crate) fn run_low_level<'a, 'ctx>(
&[
result_ptr.into(),
list_symbol_to_c_abi(env, scope, list).into(),
start,
count,
pass_update_mode(env, update_mode),
],
bitcode::STR_FROM_UTF8_RANGE,
bitcode::STR_FROM_UTF8,
);
}
}
@ -492,7 +464,7 @@ pub(crate) fn run_low_level<'a, 'ctx>(
)
}
StrRepeat => {
// Str.repeat : Str, Nat -> Str
// Str.repeat : Str, U64 -> Str
arguments!(string, count);
call_str_bitcode_fn(
@ -545,104 +517,8 @@ pub(crate) fn run_low_level<'a, 'ctx>(
);
BasicValueEnum::IntValue(is_zero)
}
StrCountGraphemes => {
// Str.countGraphemes : Str -> Nat
arguments!(string);
call_str_bitcode_fn(
env,
&[string],
&[],
BitcodeReturns::Basic,
bitcode::STR_COUNT_GRAPEHEME_CLUSTERS,
)
}
StrGetScalarUnsafe => {
// Str.getScalarUnsafe : Str, Nat -> { bytesParsed : Nat, scalar : U32 }
arguments!(string, index);
let roc_return_type =
basic_type_from_layout(env, layout_interner, layout_interner.get_repr(layout));
use roc_target::Architecture::*;
use roc_target::OperatingSystem::*;
match env.target_info {
TargetInfo {
operating_system: Windows,
..
} => {
let result = env.builder.new_build_alloca(roc_return_type, "result");
call_void_bitcode_fn(
env,
&[result.into(), string, index],
bitcode::STR_GET_SCALAR_UNSAFE,
);
let cast_result = env.builder.new_build_pointer_cast(
result,
roc_return_type.ptr_type(AddressSpace::default()),
"cast",
);
env.builder
.new_build_load(roc_return_type, cast_result, "load_result")
}
TargetInfo {
architecture: Wasm32,
..
} => {
let result = env.builder.new_build_alloca(roc_return_type, "result");
call_void_bitcode_fn(
env,
&[
result.into(),
pass_string_to_zig_wasm(env, string).into(),
index,
],
bitcode::STR_GET_SCALAR_UNSAFE,
);
let cast_result = env.builder.new_build_pointer_cast(
result,
roc_return_type.ptr_type(AddressSpace::default()),
"cast",
);
env.builder
.new_build_load(roc_return_type, cast_result, "load_result")
}
TargetInfo {
operating_system: Unix,
..
} => {
let result = call_str_bitcode_fn(
env,
&[string],
&[index],
BitcodeReturns::Basic,
bitcode::STR_GET_SCALAR_UNSAFE,
);
// zig will pad the struct to the alignment boundary, or bitpack it on 32-bit
// targets. So we have to cast it to the format that the roc code expects
let alloca = env
.builder
.new_build_alloca(result.get_type(), "to_roc_record");
env.builder.new_build_store(alloca, result);
env.builder
.new_build_load(roc_return_type, alloca, "to_roc_record")
}
TargetInfo {
operating_system: Wasi,
..
} => unimplemented!(),
}
}
StrCountUtf8Bytes => {
// Str.countUtf8Bytes : Str -> Nat
// Str.countUtf8Bytes : Str -> U64
arguments!(string);
call_str_bitcode_fn(
@ -653,14 +529,8 @@ pub(crate) fn run_low_level<'a, 'ctx>(
bitcode::STR_COUNT_UTF8_BYTES,
)
}
StrGetCapacity => {
// Str.capacity : Str -> Nat
arguments!(string);
call_bitcode_fn(env, &[string], bitcode::STR_CAPACITY)
}
StrSubstringUnsafe => {
// Str.substringUnsafe : Str, Nat, Nat -> Str
// Str.substringUnsafe : Str, U64, U64 -> Str
arguments!(string, start, length);
call_str_bitcode_fn(
@ -672,7 +542,7 @@ pub(crate) fn run_low_level<'a, 'ctx>(
)
}
StrReserve => {
// Str.reserve : Str, Nat -> Str
// Str.reserve : Str, U64 -> Str
arguments!(string, capacity);
call_str_bitcode_fn(
@ -695,18 +565,6 @@ pub(crate) fn run_low_level<'a, 'ctx>(
bitcode::STR_RELEASE_EXCESS_CAPACITY,
)
}
StrAppendScalar => {
// Str.appendScalar : Str, U32 -> Str
arguments!(string, capacity);
call_str_bitcode_fn(
env,
&[string],
&[capacity],
BitcodeReturns::Str,
bitcode::STR_APPEND_SCALAR,
)
}
StrTrim => {
// Str.trim : Str -> Str
arguments!(string);
@ -738,7 +596,7 @@ pub(crate) fn run_low_level<'a, 'ctx>(
)
}
StrWithCapacity => {
// Str.withCapacity : Nat -> Str
// Str.withCapacity : U64 -> Str
arguments!(str_len);
call_str_bitcode_fn(
@ -749,26 +607,25 @@ pub(crate) fn run_low_level<'a, 'ctx>(
bitcode::STR_WITH_CAPACITY,
)
}
StrGraphemes => {
// Str.graphemes : Str -> List Str
arguments!(string);
call_str_bitcode_fn(
env,
&[string],
&[],
BitcodeReturns::List,
bitcode::STR_GRAPHEMES,
)
}
ListLen => {
// List.len : List * -> Nat
ListLenU64 => {
// List.len : List * -> U64
arguments!(list);
list_len(env.builder, list.into_struct_value()).into()
let len_usize = list_len_usize(env.builder, list.into_struct_value());
// List.len returns U64, although length is stored as usize
env.builder
.new_build_int_cast(len_usize, env.context.i64_type(), "usize_to_u64")
.into()
}
ListLenUsize => {
// List.lenUsize : List * -> usize # used internally, not exposed
arguments!(list);
list_len_usize(env.builder, list.into_struct_value()).into()
}
ListGetCapacity => {
// List.capacity: List a -> Nat
// List.capacity: List a -> U64
arguments!(list);
call_list_bitcode_fn(
@ -780,7 +637,7 @@ pub(crate) fn run_low_level<'a, 'ctx>(
)
}
ListWithCapacity => {
// List.withCapacity : Nat -> List a
// List.withCapacity : U64 -> List a
arguments!(list_len);
let result_layout = layout;
@ -827,7 +684,7 @@ pub(crate) fn run_low_level<'a, 'ctx>(
list_prepend(env, layout_interner, original_wrapper, elem, elem_layout)
}
ListReserve => {
// List.reserve : List elem, Nat -> List elem
// List.reserve : List elem, U64 -> List elem
debug_assert_eq!(args.len(), 2);
let (list, list_layout) = scope.load_symbol_and_layout(&args[0]);
@ -853,7 +710,7 @@ pub(crate) fn run_low_level<'a, 'ctx>(
list_release_excess_capacity(env, layout_interner, list, element_layout, update_mode)
}
ListSwap => {
// List.swap : List elem, Nat, Nat -> List elem
// List.swap : List elem, U64, U64 -> List elem
debug_assert_eq!(args.len(), 3);
let (list, list_layout) = scope.load_symbol_and_layout(&args[0]);
@ -894,7 +751,7 @@ pub(crate) fn run_low_level<'a, 'ctx>(
)
}
ListDropAt => {
// List.dropAt : List elem, Nat -> List elem
// List.dropAt : List elem, U64 -> List elem
debug_assert_eq!(args.len(), 2);
let (list, list_layout) = scope.load_symbol_and_layout(&args[0]);
@ -913,7 +770,7 @@ pub(crate) fn run_low_level<'a, 'ctx>(
)
}
StrGetUnsafe => {
// Str.getUnsafe : Str, Nat -> u8
// Str.getUnsafe : Str, U64 -> u8
arguments!(wrapper_struct, elem_index);
call_str_bitcode_fn(
@ -925,7 +782,7 @@ pub(crate) fn run_low_level<'a, 'ctx>(
)
}
ListGetUnsafe => {
// List.getUnsafe : List elem, Nat -> elem
// List.getUnsafe : List elem, U64 -> elem
arguments_with_layouts!((wrapper_struct, list_layout), (element_index, _l));
list_get_unsafe(
@ -962,6 +819,31 @@ pub(crate) fn run_low_level<'a, 'ctx>(
bitcode::LIST_IS_UNIQUE,
)
}
ListClone => {
// List.clone : List a -> List a
arguments_with_layouts!((list, list_layout));
let element_layout = list_element_layout!(layout_interner, list_layout);
match update_mode {
UpdateMode::Immutable => {
//
call_list_bitcode_fn(
env,
&[list.into_struct_value()],
&[
env.alignment_intvalue(layout_interner, element_layout),
layout_width(env, layout_interner, element_layout),
],
BitcodeReturns::List,
bitcode::LIST_CLONE,
)
}
UpdateMode::InPlace => {
// we statically know the list is unique
list
}
}
}
NumToStr => {
// Num.toStr : Num a -> Str
arguments_with_layouts!((num, num_layout));
@ -1064,59 +946,6 @@ pub(crate) fn run_low_level<'a, 'ctx>(
}
}
}
NumBytesToU16 => {
arguments!(list, position);
call_list_bitcode_fn(
env,
&[list.into_struct_value()],
&[position],
BitcodeReturns::Basic,
bitcode::NUM_BYTES_TO_U16,
)
}
NumBytesToU32 => {
arguments!(list, position);
call_list_bitcode_fn(
env,
&[list.into_struct_value()],
&[position],
BitcodeReturns::Basic,
bitcode::NUM_BYTES_TO_U32,
)
}
NumBytesToU64 => {
arguments!(list, position);
call_list_bitcode_fn(
env,
&[list.into_struct_value()],
&[position],
BitcodeReturns::Basic,
bitcode::NUM_BYTES_TO_U64,
)
}
NumBytesToU128 => {
arguments!(list, position);
let ret = call_list_bitcode_fn(
env,
&[list.into_struct_value()],
&[position],
BitcodeReturns::Basic,
bitcode::NUM_BYTES_TO_U128,
);
if env.target_info.operating_system == roc_target::OperatingSystem::Windows {
// On windows the return type is not a i128, likely due to alignment
env.builder
.build_bitcast(ret, env.context.i128_type(), "empty_string")
.unwrap()
} else {
ret
}
}
NumCompare => {
arguments_with_layouts!((lhs_arg, lhs_layout), (rhs_arg, rhs_layout));
@ -1853,6 +1682,11 @@ fn build_float_binop<'ctx>(
let bd = env.builder;
let float_type = match float_width {
FloatWidth::F32 => env.context.f32_type(),
FloatWidth::F64 => env.context.f64_type(),
};
match op {
NumAdd => bd.new_build_float_add(lhs, rhs, "add_float").into(),
NumAddChecked => {
@ -1865,10 +1699,8 @@ fn build_float_binop<'ctx>(
.into_int_value();
let is_infinite = bd.new_build_not(is_finite, "negate");
let struct_type = context.struct_type(
&[context.f64_type().into(), context.bool_type().into()],
false,
);
let struct_type =
context.struct_type(&[float_type.into(), context.bool_type().into()], false);
let struct_value = {
let v1 = struct_type.const_zero();
@ -1894,10 +1726,8 @@ fn build_float_binop<'ctx>(
.into_int_value();
let is_infinite = bd.new_build_not(is_finite, "negate");
let struct_type = context.struct_type(
&[context.f64_type().into(), context.bool_type().into()],
false,
);
let struct_type =
context.struct_type(&[float_type.into(), context.bool_type().into()], false);
let struct_value = {
let v1 = struct_type.const_zero();
@ -1924,10 +1754,8 @@ fn build_float_binop<'ctx>(
.into_int_value();
let is_infinite = bd.new_build_not(is_finite, "negate");
let struct_type = context.struct_type(
&[context.f64_type().into(), context.bool_type().into()],
false,
);
let struct_type =
context.struct_type(&[float_type.into(), context.bool_type().into()], false);
let struct_value = {
let v1 = struct_type.const_zero();
@ -2179,6 +2007,54 @@ fn dec_unary_op<'ctx>(
}
}
fn dec_binary_op<'ctx>(
env: &Env<'_, 'ctx, '_>,
fn_name: &str,
dec1: BasicValueEnum<'ctx>,
dec2: BasicValueEnum<'ctx>,
) -> BasicValueEnum<'ctx> {
use roc_target::Architecture::*;
use roc_target::OperatingSystem::*;
let dec1 = dec1.into_int_value();
let dec2 = dec2.into_int_value();
match env.target_info {
TargetInfo {
architecture: X86_64 | X86_32,
operating_system: Unix,
} => {
let (low1, high1) = dec_split_into_words(env, dec1);
let (low2, high2) = dec_split_into_words(env, dec2);
let lowr_highr = call_bitcode_fn(
env,
&[low1.into(), high1.into(), low2.into(), high2.into()],
fn_name,
);
let block = env.builder.get_insert_block().expect("to be in a function");
let parent = block.get_parent().expect("to be in a function");
let ptr =
create_entry_block_alloca(env, parent, env.context.i128_type().into(), "to_i128");
env.builder.build_store(ptr, lowr_highr).unwrap();
env.builder
.build_load(env.context.i128_type(), ptr, "to_i128")
.unwrap()
}
TargetInfo {
architecture: Wasm32,
operating_system: Unix,
} => call_bitcode_fn(env, &[dec1.into(), dec2.into()], fn_name),
_ => call_bitcode_fn(
env,
&[dec_alloca(env, dec1), dec_alloca(env, dec2)],
fn_name,
),
}
}
fn dec_binop_with_overflow<'ctx>(
env: &Env<'_, 'ctx, '_>,
fn_name: &str,
@ -2309,11 +2185,13 @@ fn build_dec_unary_op<'a, 'ctx>(
_layout_interner: &STLayoutInterner<'a>,
_parent: FunctionValue<'ctx>,
arg: BasicValueEnum<'ctx>,
_return_layout: InLayout<'a>,
return_layout: InLayout<'a>,
op: LowLevel,
) -> BasicValueEnum<'ctx> {
use roc_module::low_level::LowLevel::*;
let int_width = || return_layout.to_int_width();
match op {
NumAbs => dec_unary_op(env, bitcode::DEC_ABS, arg),
NumAcos => dec_unary_op(env, bitcode::DEC_ACOS, arg),
@ -2323,6 +2201,10 @@ fn build_dec_unary_op<'a, 'ctx>(
NumSin => dec_unary_op(env, bitcode::DEC_SIN, arg),
NumTan => dec_unary_op(env, bitcode::DEC_TAN, arg),
NumRound => dec_unary_op(env, &bitcode::DEC_ROUND[int_width()], arg),
NumFloor => dec_unary_op(env, &bitcode::DEC_FLOOR[int_width()], arg),
NumCeiling => dec_unary_op(env, &bitcode::DEC_CEILING[int_width()], arg),
_ => {
unreachable!("Unrecognized dec unary operation: {:?}", op);
}
@ -2391,6 +2273,7 @@ fn build_dec_binop<'a, 'ctx>(
&[lhs, rhs],
&bitcode::NUM_GREATER_THAN_OR_EQUAL[IntWidth::I128],
),
NumPow => dec_binary_op(env, bitcode::DEC_POW, lhs, rhs),
_ => {
unreachable!("Unrecognized dec binary operation: {:?}", op);
}
@ -2804,42 +2687,39 @@ fn build_float_unary_op<'a, 'ctx>(
LayoutRepr::Builtin(Builtin::Int(int_width)) => int_width,
_ => internal_error!("Ceiling return layout is not int: {:?}", layout),
};
match float_width {
FloatWidth::F32 => {
call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_CEILING_F32[int_width])
}
FloatWidth::F64 => {
call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_CEILING_F64[int_width])
}
}
let intrinsic = match float_width {
FloatWidth::F32 => &bitcode::NUM_CEILING_F32[int_width],
FloatWidth::F64 => &bitcode::NUM_CEILING_F64[int_width],
};
call_bitcode_fn(env, &[arg.into()], intrinsic)
}
NumFloor => {
let int_width = match layout_interner.get_repr(layout) {
LayoutRepr::Builtin(Builtin::Int(int_width)) => int_width,
_ => internal_error!("Floor return layout is not int: {:?}", layout),
};
match float_width {
FloatWidth::F32 => {
call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_FLOOR_F32[int_width])
}
FloatWidth::F64 => {
call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_FLOOR_F64[int_width])
}
}
let intrinsic = match float_width {
FloatWidth::F32 => &bitcode::NUM_FLOOR_F32[int_width],
FloatWidth::F64 => &bitcode::NUM_FLOOR_F64[int_width],
};
call_bitcode_fn(env, &[arg.into()], intrinsic)
}
NumRound => {
let int_width = match layout_interner.get_repr(layout) {
LayoutRepr::Builtin(Builtin::Int(int_width)) => int_width,
_ => internal_error!("Round return layout is not int: {:?}", layout),
};
match float_width {
FloatWidth::F32 => {
call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_ROUND_F32[int_width])
}
FloatWidth::F64 => {
call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_ROUND_F64[int_width])
}
}
let intrinsic = match float_width {
FloatWidth::F32 => &bitcode::NUM_ROUND_F32[int_width],
FloatWidth::F64 => &bitcode::NUM_ROUND_F64[int_width],
};
call_bitcode_fn(env, &[arg.into()], intrinsic)
}
NumIsNan => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_IS_NAN[float_width]),
NumIsInfinite => {

View file

@ -1295,15 +1295,25 @@ fn build_rec_union_recursive_decrement<'a, 'ctx>(
_ => tag_id,
};
let block = env.context.append_basic_block(parent, "tag_id_decrement");
env.builder.position_at_end(block);
// if none of the fields are or contain anything refcounted, just move on
if fields_need_no_refcounting(layout_interner, field_layouts) {
// Still make sure to decrement the refcount of the union as a whole.
if let DecOrReuse::Dec = decrement_or_reuse {
let union_layout = LayoutRepr::Union(union_layout);
refcount_ptr.modify(call_mode, union_layout, env, layout_interner);
}
// this function returns void
builder.new_build_return(None);
cases.push((tag_id_int_type.const_int(tag_id as u64, false), block));
continue;
}
let block = env.context.append_basic_block(parent, "tag_id_decrement");
env.builder.position_at_end(block);
let fields_struct = LayoutRepr::struct_(field_layouts);
let wrapper_type = basic_type_from_layout(env, layout_interner, fields_struct);
@ -1370,12 +1380,9 @@ fn build_rec_union_recursive_decrement<'a, 'ctx>(
// and store them on the stack, then modify (and potentially free) the current cell, then
// actually inc/dec the fields.
match decrement_or_reuse {
DecOrReuse::Reuse => {}
DecOrReuse::Dec => {
let union_layout = LayoutRepr::Union(union_layout);
refcount_ptr.modify(call_mode, union_layout, env, layout_interner);
}
if let DecOrReuse::Dec = decrement_or_reuse {
let union_layout = LayoutRepr::Union(union_layout);
refcount_ptr.modify(call_mode, union_layout, env, layout_interner);
}
for (field, field_layout) in deferred_nonrec {

View file

@ -11,7 +11,7 @@ use roc_mono::layout::{InLayout, LayoutInterner, LayoutRepr, STLayoutInterner};
use crate::llvm::build::{load_roc_value, use_roc_value};
use super::{
build::{BuilderExt, Env},
build::{store_roc_value, BuilderExt, Env},
convert::basic_type_from_layout,
scope::Scope,
};
@ -50,19 +50,16 @@ impl<'ctx> RocStruct<'ctx> {
scope: &Scope<'a, 'ctx>,
sorted_fields: &[Symbol],
) -> Self {
let BuildStruct {
struct_type,
struct_val,
} = build_struct_helper(env, layout_interner, scope, sorted_fields);
let passed_by_ref = layout_repr.is_passed_by_reference(layout_interner);
if passed_by_ref {
let alloca = env.builder.new_build_alloca(struct_type, "struct_alloca");
env.builder.new_build_store(alloca, struct_val);
RocStruct::ByReference(alloca)
let struct_alloca =
build_struct_alloca_helper(env, layout_interner, scope, sorted_fields);
RocStruct::ByReference(struct_alloca)
} else {
RocStruct::ByValue(struct_val)
let struct_value =
build_struct_value_helper(env, layout_interner, scope, sorted_fields);
RocStruct::ByValue(struct_value)
}
}
@ -178,17 +175,12 @@ fn get_field_from_value<'ctx>(
.unwrap()
}
struct BuildStruct<'ctx> {
struct_type: StructType<'ctx>,
struct_val: StructValue<'ctx>,
}
fn build_struct_helper<'a, 'ctx>(
fn build_struct_value_helper<'a, 'ctx>(
env: &Env<'a, 'ctx, '_>,
layout_interner: &STLayoutInterner<'a>,
scope: &Scope<'a, 'ctx>,
sorted_fields: &[Symbol],
) -> BuildStruct<'ctx> {
) -> StructValue<'ctx> {
let ctx = env.context;
// Determine types
@ -227,12 +219,49 @@ fn build_struct_helper<'a, 'ctx>(
// Create the struct_type
let struct_type = ctx.struct_type(field_types.into_bump_slice(), false);
let struct_val = struct_from_fields(env, struct_type, field_vals.into_iter().enumerate());
struct_from_fields(env, struct_type, field_vals.into_iter().enumerate())
}
BuildStruct {
struct_type,
struct_val,
fn build_struct_alloca_helper<'a, 'ctx>(
env: &Env<'a, 'ctx, '_>,
layout_interner: &STLayoutInterner<'a>,
scope: &Scope<'a, 'ctx>,
sorted_fields: &[Symbol],
) -> PointerValue<'ctx> {
let ctx = env.context;
// Determine types
let num_fields = sorted_fields.len();
let mut field_types = AVec::with_capacity_in(num_fields, env.arena);
let mut field_expr_repr = AVec::with_capacity_in(num_fields, env.arena);
for symbol in sorted_fields.iter() {
// Zero-sized fields have no runtime representation.
// The layout of the struct expects them to be dropped!
let (field_expr, field_layout) = scope.load_symbol_and_layout(symbol);
if !layout_interner
.get_repr(field_layout)
.is_dropped_because_empty()
{
let field_repr = layout_interner.get_repr(field_layout);
let field_type = basic_type_from_layout(env, layout_interner, field_repr);
field_types.push(field_type);
field_expr_repr.push((field_expr, field_repr));
}
}
// Create the struct_type
let struct_type = ctx.struct_type(field_types.into_bump_slice(), false);
let alloca = env.builder.new_build_alloca(struct_type, "struct_alloca");
for (i, (field_expr, field_repr)) in field_expr_repr.into_iter().enumerate() {
let dst =
env.builder
.new_build_struct_gep(struct_type, alloca, i as u32, "struct_field_gep");
store_roc_value(env, layout_interner, field_repr, dst, field_expr);
}
alloca
}
pub fn struct_from_fields<'a, 'ctx, 'env, I>(

View file

@ -128,7 +128,7 @@ macro_rules! run_jit_function {
Err((error_msg, _)) => {
eprintln!("This Roc code crashed with: \"{error_msg}\"");
Expr::MalformedClosure
Expr::REPL_RUNTIME_CRASH
}
}
}};
@ -165,10 +165,10 @@ macro_rules! run_jit_function_dynamic_type {
let result = Result::from(call_result);
match result {
Ok(()) => $transform(output.add(CALL_RESULT_WIDTH) as usize),
Err((msg, _crash_tag)) => {
eprintln!("{}", msg);
panic!("Roc hit an error");
Ok(()) => Some($transform(output.add(CALL_RESULT_WIDTH) as usize)),
Err((error_msg, _)) => {
eprintln!("This Roc code crashed with: \"{error_msg}\"");
None
}
}
}

View file

@ -1,4 +0,0 @@
*.wasm
*.wat
/notes.md
/tmp

View file

@ -185,7 +185,6 @@ impl<'a> LowLevelCall<'a> {
match self.lowlevel {
// Str
StrConcat => self.load_args_and_call_zig(backend, bitcode::STR_CONCAT),
StrToScalars => self.load_args_and_call_zig(backend, bitcode::STR_TO_SCALARS),
StrGetUnsafe => self.load_args_and_call_zig(backend, bitcode::STR_GET_UNSAFE),
StrJoinWith => self.load_args_and_call_zig(backend, bitcode::STR_JOIN_WITH),
StrIsEmpty => match backend.storage.get(&self.arguments[0]) {
@ -200,18 +199,11 @@ impl<'a> LowLevelCall<'a> {
_ => internal_error!("invalid storage for Str"),
},
StrStartsWith => self.load_args_and_call_zig(backend, bitcode::STR_STARTS_WITH),
StrStartsWithScalar => {
self.load_args_and_call_zig(backend, bitcode::STR_STARTS_WITH_SCALAR)
}
StrEndsWith => self.load_args_and_call_zig(backend, bitcode::STR_ENDS_WITH),
StrSplit => self.load_args_and_call_zig(backend, bitcode::STR_SPLIT),
StrCountGraphemes => {
self.load_args_and_call_zig(backend, bitcode::STR_COUNT_GRAPEHEME_CLUSTERS)
}
StrCountUtf8Bytes => {
self.load_args_and_call_zig(backend, bitcode::STR_COUNT_UTF8_BYTES)
}
StrGetCapacity => self.load_args_and_call_zig(backend, bitcode::STR_CAPACITY),
StrToNum => {
let number_layout = match backend.layout_interner.get_repr(self.ret_layout) {
LayoutRepr::Struct(field_layouts) => field_layouts[0],
@ -233,15 +225,13 @@ impl<'a> LowLevelCall<'a> {
}
StrFromInt => self.num_to_str(backend),
StrFromFloat => self.num_to_str(backend),
StrFromUtf8Range => {
StrFromUtf8 => {
/*
Low-level op returns a struct with all the data for both Ok and Err.
Roc AST wrapper converts this to a tag union, with app-dependent tag IDs.
output: *FromUtf8Result i32
arg: RocList i32
start i32
count i32
update_mode: UpdateMode i32
*/
@ -253,7 +243,7 @@ impl<'a> LowLevelCall<'a> {
&WasmLayout::new(backend.layout_interner, self.ret_layout),
);
backend.code_builder.i32_const(UPDATE_MODE_IMMUTABLE);
backend.call_host_fn_after_loading_args(bitcode::STR_FROM_UTF8_RANGE);
backend.call_host_fn_after_loading_args(bitcode::STR_FROM_UTF8);
}
StrTrimStart => self.load_args_and_call_zig(backend, bitcode::STR_TRIM_START),
StrTrimEnd => self.load_args_and_call_zig(backend, bitcode::STR_TRIM_END),
@ -263,42 +253,48 @@ impl<'a> LowLevelCall<'a> {
self.load_args_and_call_zig(backend, bitcode::STR_RELEASE_EXCESS_CAPACITY)
}
StrRepeat => self.load_args_and_call_zig(backend, bitcode::STR_REPEAT),
StrAppendScalar => self.load_args_and_call_zig(backend, bitcode::STR_APPEND_SCALAR),
StrTrim => self.load_args_and_call_zig(backend, bitcode::STR_TRIM),
StrGetScalarUnsafe => {
self.load_args_and_call_zig(backend, bitcode::STR_GET_SCALAR_UNSAFE)
}
StrSubstringUnsafe => {
self.load_args_and_call_zig(backend, bitcode::STR_SUBSTRING_UNSAFE)
}
StrWithCapacity => self.load_args_and_call_zig(backend, bitcode::STR_WITH_CAPACITY),
StrGraphemes => self.load_args_and_call_zig(backend, bitcode::STR_GRAPHEMES),
// List
ListLen => match backend.storage.get(&self.arguments[0]) {
StoredValue::StackMemory { location, .. } => {
let (local_id, offset) =
location.local_and_offset(backend.storage.stack_frame_pointer);
backend.code_builder.get_local(local_id);
// List is stored as (pointer, length, capacity),
// with each of those fields being 4 bytes on wasm.
// So the length is 4 bytes after the start of the struct.
//
// WRAPPER_LEN represents the index of the length field
// (which is 1 as of the writing of this comment). If the field order
// ever changes, WRAPPER_LEN should be updated and this logic should
// continue to work even though this comment may become inaccurate.
backend
.code_builder
.i32_load(Align::Bytes4, offset + (4 * Builtin::WRAPPER_LEN));
}
_ => internal_error!("invalid storage for List"),
},
ListLenU64 => {
self.load_list_len_usize(backend);
// Length is stored as 32 bits in memory on wasm32,
// but List.len always returns U64
backend.code_builder.i64_extend_u_i32();
}
ListLenUsize => self.load_list_len_usize(backend),
ListGetCapacity => self.load_args_and_call_zig(backend, bitcode::LIST_CAPACITY),
ListIsUnique => self.load_args_and_call_zig(backend, bitcode::LIST_IS_UNIQUE),
ListClone => {
let input_list: Symbol = self.arguments[0];
let elem_layout = unwrap_list_elem_layout(self.ret_layout_raw);
let elem_layout = backend.layout_interner.get_repr(elem_layout);
let (elem_width, elem_align) =
elem_layout.stack_size_and_alignment(backend.layout_interner);
// Zig arguments Wasm types
// (return pointer) i32
// input_list: &RocList i32
// alignment: u32 i32
// element_width: usize i32
backend
.storage
.load_symbols(&mut backend.code_builder, &[self.ret_symbol, input_list]);
backend.code_builder.i32_const(elem_align as i32);
backend.code_builder.i32_const(elem_width as i32);
backend.call_host_fn_after_loading_args(bitcode::LIST_CLONE);
}
ListMap | ListMap2 | ListMap3 | ListMap4 | ListSortWith => {
internal_error!("HigherOrder lowlevels should not be handled here")
}
@ -311,6 +307,7 @@ impl<'a> LowLevelCall<'a> {
backend
.storage
.load_symbols(&mut backend.code_builder, &[index]);
backend.code_builder.i32_wrap_i64(); // listGetUnsafe takes a U64, but we do 32-bit indexing on wasm.
let elem_size = backend.layout_interner.stack_size(self.ret_layout);
backend.code_builder.i32_const(elem_size as i32);
backend.code_builder.i32_mul(); // index*size
@ -347,7 +344,7 @@ impl<'a> LowLevelCall<'a> {
}
}
ListReplaceUnsafe => {
// List.replace_unsafe : List elem, Nat, elem -> { list: List elem, value: elem }
// List.replace_unsafe : List elem, U64, elem -> { list: List elem, value: elem }
let list: Symbol = self.arguments[0];
let index: Symbol = self.arguments[1];
@ -435,7 +432,7 @@ impl<'a> LowLevelCall<'a> {
backend.call_host_fn_after_loading_args(bitcode::LIST_REPLACE);
}
ListWithCapacity => {
// List.withCapacity : Nat -> List elem
// List.withCapacity : U64 -> List elem
let capacity: Symbol = self.arguments[0];
let elem_layout = unwrap_list_elem_layout(self.ret_layout_raw);
@ -445,7 +442,7 @@ impl<'a> LowLevelCall<'a> {
// Zig arguments Wasm types
// (return pointer) i32
// capacity: usize i32
// capacity: u64 i64
// alignment: u32 i32
// element_width: usize i32
@ -486,7 +483,7 @@ impl<'a> LowLevelCall<'a> {
}
ListReserve => {
// List.reserve : List elem, Nat -> List elem
// List.reserve : List elem, U64 -> List elem
let list: Symbol = self.arguments[0];
let spare: Symbol = self.arguments[1];
@ -500,7 +497,7 @@ impl<'a> LowLevelCall<'a> {
// (return pointer) i32
// list: RocList i32
// alignment: u32 i32
// spare: usize i32
// spare: u64 i64
// element_width: usize i32
// update_mode: UpdateMode i32
@ -635,7 +632,7 @@ impl<'a> LowLevelCall<'a> {
}
ListSublist => {
// As a low-level, record is destructured
// List.sublist : List elem, start : Nat, len : Nat -> List elem
// List.sublist : List elem, start : U64, len : U64 -> List elem
let list: Symbol = self.arguments[0];
let start: Symbol = self.arguments[1];
@ -662,8 +659,8 @@ impl<'a> LowLevelCall<'a> {
// list: RocList, i32
// alignment: u32, i32
// element_width: usize, i32
// start: usize, i32
// len: usize, i32
// start: u64, i64
// len: u64, i64
// dec: Dec, i32
backend.storage.load_symbols_for_call(
@ -683,7 +680,7 @@ impl<'a> LowLevelCall<'a> {
backend.call_host_fn_after_loading_args(bitcode::LIST_SUBLIST);
}
ListDropAt => {
// List.dropAt : List elem, Nat -> List elem
// List.dropAt : List elem, U64 -> List elem
let list: Symbol = self.arguments[0];
let drop_index: Symbol = self.arguments[1];
@ -708,7 +705,7 @@ impl<'a> LowLevelCall<'a> {
// list: RocList, i32
// element_width: usize, i32
// alignment: u32, i32
// drop_index: usize, i32
// drop_index: u64, i64
// dec: Dec, i32
// Load the return pointer and the list
@ -729,7 +726,7 @@ impl<'a> LowLevelCall<'a> {
backend.call_host_fn_after_loading_args(bitcode::LIST_DROP_AT);
}
ListSwap => {
// List.swap : List elem, Nat, Nat -> List elem
// List.swap : List elem, U64, U64 -> List elem
let list: Symbol = self.arguments[0];
let index_1: Symbol = self.arguments[1];
let index_2: Symbol = self.arguments[2];
@ -744,8 +741,8 @@ impl<'a> LowLevelCall<'a> {
// list: RocList, i32
// alignment: u32, i32
// element_width: usize, i32
// index_1: usize, i32
// index_2: usize, i32
// index_1: u64, i64
// index_2: u64, i64
// update_mode: UpdateMode, i32
// Load the return pointer and the list
@ -1575,6 +1572,10 @@ impl<'a> LowLevelCall<'a> {
LayoutRepr::Builtin(Builtin::Float(width)) => {
self.load_args_and_call_zig(backend, &bitcode::NUM_POW[width]);
}
LayoutRepr::Builtin(Builtin::Decimal) => {
self.load_args_and_call_zig(backend, bitcode::DEC_POW);
}
_ => panic_ret_type(),
},
@ -1626,6 +1627,7 @@ impl<'a> LowLevelCall<'a> {
match arg_type {
F32 => self.load_args_and_call_zig(backend, &bitcode::NUM_ROUND_F32[width]),
F64 => self.load_args_and_call_zig(backend, &bitcode::NUM_ROUND_F64[width]),
Decimal => self.load_args_and_call_zig(backend, &bitcode::DEC_ROUND[width]),
_ => internal_error!("Invalid argument type for round: {:?}", arg_type),
}
}
@ -1633,6 +1635,14 @@ impl<'a> LowLevelCall<'a> {
self.load_args(backend);
let arg_type = CodeGenNumType::for_symbol(backend, self.arguments[0]);
let ret_type = CodeGenNumType::from(self.ret_layout);
let width = match ret_type {
CodeGenNumType::I32 => IntWidth::I32,
CodeGenNumType::I64 => IntWidth::I64,
CodeGenNumType::I128 => todo!("{:?} for I128", self.lowlevel),
_ => internal_error!("Invalid return type for round: {:?}", ret_type),
};
match (arg_type, self.lowlevel) {
(F32, NumCeiling) => {
backend.code_builder.f32_ceil();
@ -1640,14 +1650,21 @@ impl<'a> LowLevelCall<'a> {
(F64, NumCeiling) => {
backend.code_builder.f64_ceil();
}
(Decimal, NumCeiling) => {
return self.load_args_and_call_zig(backend, &bitcode::DEC_CEILING[width]);
}
(F32, NumFloor) => {
backend.code_builder.f32_floor();
}
(F64, NumFloor) => {
backend.code_builder.f64_floor();
}
(Decimal, NumFloor) => {
return self.load_args_and_call_zig(backend, &bitcode::DEC_FLOOR[width]);
}
_ => internal_error!("Invalid argument type for ceiling: {:?}", arg_type),
}
match (ret_type, arg_type) {
// TODO: unsigned truncation
(I32, F32) => backend.code_builder.i32_trunc_s_f32(),
@ -1708,10 +1725,6 @@ impl<'a> LowLevelCall<'a> {
}
_ => panic_ret_type(),
},
NumBytesToU16 => self.load_args_and_call_zig(backend, bitcode::NUM_BYTES_TO_U16),
NumBytesToU32 => self.load_args_and_call_zig(backend, bitcode::NUM_BYTES_TO_U32),
NumBytesToU64 => self.load_args_and_call_zig(backend, bitcode::NUM_BYTES_TO_U64),
NumBytesToU128 => self.load_args_and_call_zig(backend, bitcode::NUM_BYTES_TO_U128),
NumBitwiseAnd => {
self.load_args(backend);
match CodeGenNumType::from(self.ret_layout) {
@ -2090,6 +2103,28 @@ impl<'a> LowLevelCall<'a> {
}
}
fn load_list_len_usize(&self, backend: &mut WasmBackend<'_, '_>) {
match backend.storage.get(&self.arguments[0]) {
StoredValue::StackMemory { location, .. } => {
let (local_id, offset) =
location.local_and_offset(backend.storage.stack_frame_pointer);
backend.code_builder.get_local(local_id);
// List is stored as (pointer, length, capacity),
// with each of those fields being 4 bytes on wasm.
// So the length is 4 bytes after the start of the struct.
//
// WRAPPER_LEN represents the index of the length field
// (which is 1 as of the writing of this comment). If the field order
// ever changes, WRAPPER_LEN should be updated and this logic should
// continue to work even though this comment may become inaccurate.
backend
.code_builder
.i32_load(Align::Bytes4, offset + (4 * Builtin::WRAPPER_LEN));
}
_ => internal_error!("invalid storage for List"),
}
}
/// Equality and inequality
/// These can operate on any data type (except functions) so they're more complex than other operators.
fn eq_or_neq(&self, backend: &mut WasmBackend<'a, '_>) {

View file

@ -22,8 +22,21 @@ macro_rules! wasm32_sized_primitive {
}
}
wasm32_sized_primitive!(u8, i8, u16, i16, u32, i32, char, u64, i64, u128, i128, f32, f64, bool,);
wasm32_sized_primitive!(RocDec, RocOrder, I128, U128,);
wasm32_sized_primitive!(u8, i8, u16, i16, u32, i32, char, u64, i64, f32, f64, bool,);
wasm32_sized_primitive!(RocOrder,);
macro_rules! wasm32_16byte_aligned8 {
($($type_name:ident ,)+) => {
$(
impl Wasm32Sized for $type_name {
const SIZE_OF_WASM: usize = 16;
const ALIGN_OF_WASM: usize = 8;
}
)*
}
}
wasm32_16byte_aligned8!(i128, u128, I128, U128, RocDec,);
impl Wasm32Sized for () {
const SIZE_OF_WASM: usize = 0;
@ -75,19 +88,36 @@ impl Wasm32Sized for isize {
const ALIGN_OF_WASM: usize = 4;
}
const fn next_multiple_of(lhs: usize, rhs: usize) -> usize {
if lhs == 0 {
return lhs;
}
match lhs % rhs {
0 => lhs,
r => lhs + (rhs - r),
}
}
impl<T: Wasm32Sized, U: Wasm32Sized> Wasm32Sized for (T, U) {
const SIZE_OF_WASM: usize = T::SIZE_OF_WASM + U::SIZE_OF_WASM;
const SIZE_OF_WASM: usize =
next_multiple_of(T::SIZE_OF_WASM + U::SIZE_OF_WASM, Self::ALIGN_OF_WASM);
const ALIGN_OF_WASM: usize = max(&[T::ALIGN_OF_WASM, U::ALIGN_OF_WASM]);
}
impl<T: Wasm32Sized, U: Wasm32Sized, V: Wasm32Sized> Wasm32Sized for (T, U, V) {
const SIZE_OF_WASM: usize = T::SIZE_OF_WASM + U::SIZE_OF_WASM + V::SIZE_OF_WASM;
const SIZE_OF_WASM: usize = next_multiple_of(
T::SIZE_OF_WASM + U::SIZE_OF_WASM + V::SIZE_OF_WASM,
Self::ALIGN_OF_WASM,
);
const ALIGN_OF_WASM: usize = max(&[T::ALIGN_OF_WASM, U::ALIGN_OF_WASM, V::ALIGN_OF_WASM]);
}
impl<T: Wasm32Sized, U: Wasm32Sized, V: Wasm32Sized, W: Wasm32Sized> Wasm32Sized for (T, U, V, W) {
const SIZE_OF_WASM: usize =
T::SIZE_OF_WASM + U::SIZE_OF_WASM + V::SIZE_OF_WASM + W::SIZE_OF_WASM;
const SIZE_OF_WASM: usize = next_multiple_of(
T::SIZE_OF_WASM + U::SIZE_OF_WASM + V::SIZE_OF_WASM + W::SIZE_OF_WASM,
Self::ALIGN_OF_WASM,
);
const ALIGN_OF_WASM: usize = max(&[
T::ALIGN_OF_WASM,
U::ALIGN_OF_WASM,

View file

@ -29,11 +29,8 @@ roc_reporting = { path = "../../reporting" }
roc_solve = { path = "../solve" }
roc_target = { path = "../roc_target" }
roc_error_macros = { path = "../../error_macros" }
bumpalo.workspace = true
[target.'cfg(not(windows))'.build-dependencies]
roc_load_internal = { path = "../load_internal" }
bumpalo.workspace = true
[dev-dependencies]
roc_constrain = { path = "../constrain" }

View file

@ -1,6 +1,5 @@
use std::path::{Path, PathBuf};
#[cfg(not(windows))]
use bumpalo::Bump;
use roc_error_macros::internal_error;
use roc_module::symbol::ModuleId;
@ -47,19 +46,11 @@ fn write_subs_for_module(module_id: ModuleId, filename: &str) {
output_path.extend([filename]);
output_path.set_extension("dat");
#[cfg(not(windows))]
if SKIP_SUBS_CACHE {
write_types_for_module_dummy(&output_path)
} else {
write_types_for_module_real(module_id, filename, &output_path)
}
#[cfg(windows)]
{
let _ = SKIP_SUBS_CACHE;
let _ = module_id;
write_types_for_module_dummy(&output_path)
}
}
fn write_types_for_module_dummy(output_path: &Path) {
@ -67,7 +58,6 @@ fn write_types_for_module_dummy(output_path: &Path) {
std::fs::write(output_path, []).unwrap();
}
#[cfg(not(windows))]
fn write_types_for_module_real(module_id: ModuleId, filename: &str, output_path: &Path) {
use roc_can::module::TypeState;
use roc_load_internal::file::{LoadingProblem, Threading};

View file

@ -247,7 +247,7 @@ fn read_cached_types() -> MutMap<ModuleId, TypeState> {
// Wasm seems to re-order definitions between build time and runtime, but only in release mode.
// That is very strange, but we can solve it separately
if !cfg!(target_family = "wasm") && !cfg!(windows) && !SKIP_SUBS_CACHE {
if !cfg!(target_family = "wasm") && !SKIP_SUBS_CACHE {
output.insert(ModuleId::BOOL, deserialize_help(mod_bool));
output.insert(ModuleId::RESULT, deserialize_help(mod_result));

File diff suppressed because it is too large Load diff

View file

@ -1 +0,0 @@
/tmp

View file

@ -2174,6 +2174,38 @@ fn report_unused_imported_modules(
}
}
///Generates an errorfor modules that are imported from packages that don't exist
///TODO. This is temporary. Remove this once module params is implemented
fn check_for_missing_package_shorthand_in_cache<'a>(
header: &ModuleHeader,
shorthands: &Arc<Mutex<MutMap<&'a str, ShorthandPath>>>,
) -> Result<(), LoadingProblem<'a>> {
header.package_qualified_imported_modules
.iter()
.find_map(|pqim| match pqim {
PackageQualified::Unqualified(_) => None,
PackageQualified::Qualified(shorthand, _) => {
if!(shorthands.lock().iter().any(|(short,_)|short==shorthand)){
let module_path=header.module_path.to_str().unwrap_or("");
Some(LoadingProblem::FormattedReport(
match header.header_type {
HeaderType::Hosted { ..} |
HeaderType::Builtin {..} |
HeaderType::Interface {..} =>
{
let mod_type= header.header_type.to_string();
format!("The package shorthand '{shorthand}' that you are using in the 'imports' section of the header of module '{module_path}' doesn't exist.\nCheck that package shorthand is correct or reference the package in an 'app' or 'package' header.\nThis module is an {mod_type}, because of a bug in the compiler we are unable to directly typecheck {mod_type} modules with package imports so this error may not be correct. Please start checking at an app, package or platform file that imports this file.")
},
_=>
format!("The package shorthand '{shorthand}' that you are using in the 'imports' section of the header of module '{module_path}' doesn't exist.\nCheck that package shorthand is correct or reference the package in an 'app' or 'package' header.")
}))
} else {
None
}
}
}).map_or(Ok(()),Err)
}
fn extend_header_with_builtin(header: &mut ModuleHeader, module: ModuleId) {
header
.package_qualified_imported_modules
@ -2257,7 +2289,9 @@ fn update<'a>(
#[cfg(target_family = "wasm")]
{
panic!("Specifying packages via URLs is curently unsupported in wasm.");
panic!(
"Specifying packages via URLs is currently unsupported in wasm."
);
}
} else {
// This wasn't a URL, so it must be a filesystem path.
@ -2387,6 +2421,7 @@ fn update<'a>(
}
}
check_for_missing_package_shorthand_in_cache(&header, &state.arc_shorthands)?;
// store an ID to name mapping, so we know the file to read when fetching dependencies' headers
for (name, id) in header.deps_by_name.iter() {
state.module_cache.module_names.insert(*id, name.clone());
@ -3803,6 +3838,35 @@ struct HeaderOutput<'a> {
opt_platform_shorthand: Option<&'a str>,
}
fn ensure_roc_file<'a>(filename: &Path, src_bytes: &[u8]) -> Result<(), LoadingProblem<'a>> {
match filename.extension() {
Some(ext) => {
if ext != ROC_FILE_EXTENSION {
return Err(LoadingProblem::FileProblem {
filename: filename.to_path_buf(),
error: io::ErrorKind::Unsupported,
});
}
}
None => {
let index = src_bytes
.iter()
.position(|a| *a == b'\n')
.unwrap_or(src_bytes.len());
let frist_line_bytes = src_bytes[0..index].to_vec();
if let Ok(first_line) = String::from_utf8(frist_line_bytes) {
if !(first_line.starts_with("#!") && first_line.contains("roc")) {
return Err(LoadingProblem::FileProblem {
filename: filename.to_path_buf(),
error: std::io::ErrorKind::Unsupported,
});
}
}
}
}
Ok(())
}
fn parse_header<'a>(
arena: &'a Bump,
read_file_duration: Duration,
@ -3821,6 +3885,8 @@ fn parse_header<'a>(
let parsed = roc_parse::module::parse_header(arena, parse_state.clone());
let parse_header_duration = parse_start.elapsed();
ensure_roc_file(&filename, src_bytes)?;
// Insert the first entries for this module's timings
let mut module_timing = ModuleTiming::new(start_time);
@ -3936,6 +4002,11 @@ fn parse_header<'a>(
} else {
&[]
};
let imports = if let Some(imports) = header.imports {
unspace(arena, imports.item.items)
} else {
&[]
};
let mut provides = bumpalo::collections::Vec::new_in(arena);
@ -3955,11 +4026,7 @@ fn parse_header<'a>(
is_root_module,
opt_shorthand,
packages,
imports: if let Some(imports) = header.imports {
unspace(arena, imports.item.items)
} else {
&[]
},
imports,
header_type: HeaderType::App {
provides: provides.into_bump_slice(),
output_name: header.name.value,
@ -4143,7 +4210,7 @@ fn load_packages<'a>(
#[cfg(target_family = "wasm")]
{
panic!("Specifying packages via URLs is curently unsupported in wasm.");
panic!("Specifying packages via URLs is currently unsupported in wasm.");
}
} else {
cwd.join(src)
@ -4628,7 +4695,7 @@ fn synth_import(subs: &mut Subs, content: roc_types::subs::Content) -> Variable
fn synth_list_len_type(subs: &mut Subs) -> Variable {
use roc_types::subs::{Content, FlatType, LambdaSet, OptVariable, SubsSlice, UnionLabels};
// List.len : List a -> Nat
// List.len : List a -> U64
let a = synth_import(subs, Content::FlexVar(None));
let a_slice = SubsSlice::extend_new(&mut subs.variables, [a]);
let list_a = synth_import(
@ -4636,7 +4703,7 @@ fn synth_list_len_type(subs: &mut Subs) -> Variable {
Content::Structure(FlatType::Apply(Symbol::LIST_LIST, a_slice)),
);
let fn_var = synth_import(subs, Content::Error);
let solved_list_len = UnionLabels::insert_into_subs(subs, [(Symbol::LIST_LEN, [])]);
let solved_list_len = UnionLabels::insert_into_subs(subs, [(Symbol::LIST_LEN_U64, [])]);
let clos_list_len = synth_import(
subs,
Content::LambdaSet(LambdaSet {
@ -4649,7 +4716,7 @@ fn synth_list_len_type(subs: &mut Subs) -> Variable {
let fn_args_slice = SubsSlice::extend_new(&mut subs.variables, [list_a]);
subs.set_content(
fn_var,
Content::Structure(FlatType::Func(fn_args_slice, clos_list_len, Variable::NAT)),
Content::Structure(FlatType::Func(fn_args_slice, clos_list_len, Variable::U64)),
);
fn_var
}
@ -4690,7 +4757,7 @@ pub fn add_imports(
// Num needs List.len, but List imports Num.
let list_len_type_var = synth_list_len_type(subs);
let list_len_type_index = constraints.push_variable(list_len_type_var);
def_types.push((Symbol::LIST_LEN, Loc::at_zero(list_len_type_index)));
def_types.push((Symbol::LIST_LEN_U64, Loc::at_zero(list_len_type_index)));
import_variables.push(list_len_type_var);
}
@ -4828,7 +4895,7 @@ fn import_variable_for_symbol(
// Today we define builtins in each module that uses them
// so even though they have a different module name from
// the surrounding module, they are not technically imported
debug_assert!(symbol.is_builtin());
debug_assert!(symbol.is_builtin(), "The symbol {:?} was not found and was assumed to be a builtin, but it wasn't a builtin.", symbol);
return;
}
AbilityMemberMustBeAvailable => {

View file

@ -0,0 +1,22 @@
fn report_missing_package_shorthand2<'a>(
packages: &[Loc<PackageEntry>],
imports: &[Loc<ImportsEntry>],
) -> Option<LoadingProblem<'a>> {
imports.iter().find_map(|i| match i.value {
ImportsEntry::Module(_, _) | ImportsEntry::IngestedFile(_, _) => None,
ImportsEntry::Package(shorthand, name, _) => {
let name=name.as_str();
if packages
.iter()
.find(|p| p.value.shorthand == shorthand)
.is_none()
{
Some(
LoadingProblem::FormattedReport(
format!("The package shorthand '{shorthand}' that you are importing the module '{name}' from in '{shorthand}.{name}', doesn't exist in this module.\nImport it in the \"packages\" section of the header.")))
} else {
None
}
}
})
}

View file

@ -1,6 +1,6 @@
app "quicksort" provides [swap, partition, partitionHelp, quicksort] to "./platform"
quicksort : List (Num a), Nat, Nat -> List (Num a)
quicksort : List (Num a), U64, U64 -> List (Num a)
quicksort = \list, low, high ->
when partition low high list is
Pair partitionIndex partitioned ->
@ -9,7 +9,7 @@ quicksort = \list, low, high ->
|> quicksort (partitionIndex + 1) high
swap : Nat, Nat, List a -> List a
swap : U64, U64, List a -> List a
swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) ->
@ -21,7 +21,7 @@ swap = \i, j, list ->
[]
partition : Nat, Nat, List (Num a) -> [Pair Nat (List (Num a))]
partition : U64, U64, List (Num a) -> [Pair U64 (List (Num a))]
partition = \low, high, initialList ->
when List.get initialList high is
Ok pivot ->
@ -33,7 +33,7 @@ partition = \low, high, initialList ->
Pair (low - 1) initialList
partitionHelp : Nat, Nat, List (Num a), Nat, (Num a) -> [Pair Nat (List (Num a))]
partitionHelp : U64, U64, List (Num a), U64, (Num a) -> [Pair U64 (List (Num a))]
partitionHelp = \i, j, list, high, pivot ->
if j < high then
when List.get list j is

View file

@ -1,6 +1,6 @@
app "quicksort" provides [quicksort] to "./platform"
quicksortHelp : List (Num a), Nat, Nat -> List (Num a)
quicksortHelp : List (Num a), U64, U64 -> List (Num a)
quicksortHelp = \list, low, high ->
if low < high then
when partition low high list is
@ -12,7 +12,7 @@ quicksortHelp = \list, low, high ->
list
swap : Nat, Nat, List a -> List a
swap : U64, U64, List a -> List a
swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) ->
@ -23,7 +23,7 @@ swap = \i, j, list ->
_ ->
[]
partition : Nat, Nat, List (Num a) -> [Pair Nat (List (Num a))]
partition : U64, U64, List (Num a) -> [Pair U64 (List (Num a))]
partition = \low, high, initialList ->
when List.get initialList high is
Ok pivot ->
@ -35,7 +35,7 @@ partition = \low, high, initialList ->
Pair (low - 1) initialList
partitionHelp : Nat, Nat, List (Num a), Nat, (Num a) -> [Pair Nat (List (Num a))]
partitionHelp : U64, U64, List (Num a), U64, (Num a) -> [Pair U64 (List (Num a))]
partitionHelp = \i, j, list, high, pivot ->
if j < high then
when List.get list j is

View file

@ -2,7 +2,7 @@ interface Quicksort
exposes [swap, partition, quicksort]
imports []
quicksort : List (Num a), Nat, Nat -> List (Num a)
quicksort : List (Num a), U64, U64 -> List (Num a)
quicksort = \list, low, high ->
when partition low high list is
Pair partitionIndex partitioned ->
@ -11,7 +11,7 @@ quicksort = \list, low, high ->
|> quicksort (partitionIndex + 1) high
swap : Nat, Nat, List a -> List a
swap : U64, U64, List a -> List a
swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) ->
@ -23,7 +23,7 @@ swap = \i, j, list ->
[]
partition : Nat, Nat, List (Num a) -> [Pair Nat (List (Num a))]
partition : U64, U64, List (Num a) -> [Pair U64 (List (Num a))]
partition = \low, high, initialList ->
when List.get initialList high is
Ok pivot ->
@ -35,7 +35,7 @@ partition = \low, high, initialList ->
Pair (low - 1) initialList
partitionHelp : Nat, Nat, List (Num a), Nat, (Num a) -> [Pair Nat (List (Num a))]
partitionHelp : U64, U64, List (Num a), U64, (Num a) -> [Pair U64 (List (Num a))]
partitionHelp = \i, j, list, high, pivot ->
if j < high then
when List.get list j is

View file

@ -26,9 +26,9 @@ use roc_module::symbol::{Interns, ModuleId};
use roc_packaging::cache::RocCacheDir;
use roc_problem::can::Problem;
use roc_region::all::LineInfo;
use roc_reporting::report::RenderTarget;
use roc_reporting::report::RocDocAllocator;
use roc_reporting::report::{can_problem, DEFAULT_PALETTE};
use roc_reporting::report::{strip_colors, RenderTarget};
use roc_solve::FunctionKind;
use roc_target::TargetInfo;
use roc_types::pretty_print::name_and_print_var;
@ -323,7 +323,7 @@ fn import_transitive_alias() {
// with variables in the importee
let modules = vec![
(
"RBTree",
"RBTree.roc",
indoc!(
r"
interface RBTree exposes [RedBlackTree, empty] imports []
@ -341,7 +341,7 @@ fn import_transitive_alias() {
),
),
(
"Other",
"Other.roc",
indoc!(
r"
interface Other exposes [empty] imports [RBTree]
@ -465,10 +465,10 @@ fn iface_quicksort() {
expect_types(
loaded_module,
hashmap! {
"swap" => "Nat, Nat, List a -> List a",
"partition" => "Nat, Nat, List (Num a) -> [Pair Nat (List (Num a))]",
"partitionHelp" => "Nat, Nat, List (Num a), Nat, Num a -> [Pair Nat (List (Num a))]",
"quicksort" => "List (Num a), Nat, Nat -> List (Num a)",
"swap" => "U64, U64, List a -> List a",
"partition" => "U64, U64, List (Num a) -> [Pair U64 (List (Num a))]",
"partitionHelp" => "U64, U64, List (Num a), U64, Num a -> [Pair U64 (List (Num a))]",
"quicksort" => "List (Num a), U64, U64 -> List (Num a)",
},
);
}
@ -481,10 +481,10 @@ fn quicksort_one_def() {
expect_types(
loaded_module,
hashmap! {
"swap" => "Nat, Nat, List a -> List a",
"partition" => "Nat, Nat, List (Num a) -> [Pair Nat (List (Num a))]",
"partitionHelp" => "Nat, Nat, List (Num a), Nat, Num a -> [Pair Nat (List (Num a))]",
"quicksortHelp" => "List (Num a), Nat, Nat -> List (Num a)",
"swap" => "U64, U64, List a -> List a",
"partition" => "U64, U64, List (Num a) -> [Pair U64 (List (Num a))]",
"partitionHelp" => "U64, U64, List (Num a), U64, Num a -> [Pair U64 (List (Num a))]",
"quicksortHelp" => "List (Num a), U64, U64 -> List (Num a)",
"quicksort" => "List (Num a) -> List (Num a)",
},
);
@ -498,10 +498,10 @@ fn app_quicksort() {
expect_types(
loaded_module,
hashmap! {
"swap" => "Nat, Nat, List a -> List a",
"partition" => "Nat, Nat, List (Num a) -> [Pair Nat (List (Num a))]",
"partitionHelp" => "Nat, Nat, List (Num a), Nat, Num a -> [Pair Nat (List (Num a))]",
"quicksort" => "List (Num a), Nat, Nat -> List (Num a)",
"swap" => "U64, U64, List a -> List a",
"partition" => "U64, U64, List (Num a) -> [Pair U64 (List (Num a))]",
"partitionHelp" => "U64, U64, List (Num a), U64, Num a -> [Pair U64 (List (Num a))]",
"quicksort" => "List (Num a), U64, U64 -> List (Num a)",
},
);
}
@ -626,7 +626,7 @@ fn ingested_file_bytes() {
#[test]
fn parse_problem() {
let modules = vec![(
"Main",
"Main.roc",
indoc!(
r"
interface Main exposes [main] imports []
@ -641,7 +641,7 @@ fn parse_problem() {
report,
indoc!(
"
UNFINISHED LIST tmp/parse_problem/Main
UNFINISHED LIST in tmp/parse_problem/Main.roc
I am partway through started parsing a list, but I got stuck here:
@ -707,7 +707,7 @@ fn ingested_file_not_found() {
#[test]
fn platform_does_not_exist() {
let modules = vec![(
"Main",
"main.roc",
indoc!(
r#"
app "example"
@ -753,7 +753,7 @@ fn platform_parse_error() {
),
),
(
"Main",
"main.roc",
indoc!(
r#"
app "hello-world"
@ -797,7 +797,7 @@ fn platform_exposes_main_return_by_pointer_issue() {
),
),
(
"Main",
"main.roc",
indoc!(
r#"
app "hello-world"
@ -818,7 +818,7 @@ fn platform_exposes_main_return_by_pointer_issue() {
fn opaque_wrapped_unwrapped_outside_defining_module() {
let modules = vec![
(
"Age",
"Age.roc",
indoc!(
r"
interface Age exposes [Age] imports []
@ -828,7 +828,7 @@ fn opaque_wrapped_unwrapped_outside_defining_module() {
),
),
(
"Main",
"Main.roc",
indoc!(
r"
interface Main exposes [twenty, readAge] imports [Age.{ Age }]
@ -847,7 +847,7 @@ fn opaque_wrapped_unwrapped_outside_defining_module() {
err,
indoc!(
r"
OPAQUE TYPE DECLARED OUTSIDE SCOPE ...rapped_outside_defining_module/Main
OPAQUE TYPE DECLARED OUTSIDE SCOPE in ...d_outside_defining_module/Main.roc
The unwrapped opaque type Age referenced here:
@ -861,7 +861,7 @@ fn opaque_wrapped_unwrapped_outside_defining_module() {
Note: Opaque types can only be wrapped and unwrapped in the module they are defined in!
OPAQUE TYPE DECLARED OUTSIDE SCOPE ...rapped_outside_defining_module/Main
OPAQUE TYPE DECLARED OUTSIDE SCOPE in ...d_outside_defining_module/Main.roc
The unwrapped opaque type Age referenced here:
@ -875,7 +875,7 @@ fn opaque_wrapped_unwrapped_outside_defining_module() {
Note: Opaque types can only be wrapped and unwrapped in the module they are defined in!
UNUSED IMPORT tmp/opaque_wrapped_unwrapped_outside_defining_module/Main
UNUSED IMPORT in ...aque_wrapped_unwrapped_outside_defining_module/Main.roc
Nothing from Age is used in this module.
@ -910,7 +910,7 @@ fn issue_2863_module_type_does_not_exist() {
),
),
(
"Main",
"main.roc",
indoc!(
r#"
app "test"
@ -930,7 +930,7 @@ fn issue_2863_module_type_does_not_exist() {
report,
indoc!(
"
UNRECOGNIZED NAME tmp/issue_2863_module_type_does_not_exist/Main
UNRECOGNIZED NAME in tmp/issue_2863_module_type_does_not_exist/main.roc
Nothing is named `DoesNotExist` in this scope.
@ -971,7 +971,7 @@ fn import_builtin_in_platform_and_check_app() {
),
),
(
"Main",
"main.roc",
indoc!(
r#"
app "test"
@ -991,7 +991,7 @@ fn import_builtin_in_platform_and_check_app() {
#[test]
fn module_doesnt_match_file_path() {
let modules = vec![(
"Age",
"Age.roc",
indoc!(
r"
interface NotAge exposes [Age] imports []
@ -1006,7 +1006,7 @@ fn module_doesnt_match_file_path() {
err,
indoc!(
r"
WEIRD MODULE NAME tmp/module_doesnt_match_file_path/Age
WEIRD MODULE NAME in tmp/module_doesnt_match_file_path/Age.roc
This module name does not correspond with the file path it is defined
in:
@ -1026,7 +1026,7 @@ fn module_doesnt_match_file_path() {
#[test]
fn module_cyclic_import_itself() {
let modules = vec![(
"Age",
"Age.roc",
indoc!(
r"
interface Age exposes [] imports [Age]
@ -1039,7 +1039,7 @@ fn module_cyclic_import_itself() {
err,
indoc!(
r"
IMPORT CYCLE tmp/module_cyclic_import_itself/Age
IMPORT CYCLE in tmp/module_cyclic_import_itself/Age.roc
I can't compile Age because it depends on itself through the following
chain of module imports:
@ -1058,12 +1058,11 @@ fn module_cyclic_import_itself() {
err
);
}
#[test]
fn module_cyclic_import_transitive() {
let modules = vec![
(
"Age",
"Age.roc",
indoc!(
r"
interface Age exposes [] imports [Person]
@ -1071,7 +1070,7 @@ fn module_cyclic_import_transitive() {
),
),
(
"Person",
"Person.roc",
indoc!(
r"
interface Person exposes [] imports [Age]
@ -1085,7 +1084,7 @@ fn module_cyclic_import_transitive() {
err,
indoc!(
r"
IMPORT CYCLE tmp/module_cyclic_import_transitive/Age.roc
IMPORT CYCLE in tmp/module_cyclic_import_transitive/Age.roc
I can't compile Age because it depends on itself through the following
chain of module imports:
@ -1133,7 +1132,7 @@ fn nested_module_has_incorrect_name() {
err,
indoc!(
r"
INCORRECT MODULE NAME tmp/nested_module_has_incorrect_name/Dep/Foo.roc
INCORRECT MODULE NAME in tmp/nested_module_has_incorrect_name/Dep/Foo.roc
This module has a different name than I expected:
@ -1148,3 +1147,114 @@ fn nested_module_has_incorrect_name() {
err
);
}
#[test]
fn module_interface_with_qualified_import() {
let modules = vec![(
"A.roc",
indoc!(
r"
interface A exposes [] imports [b.T]
"
),
)];
let err = multiple_modules("module_interface_with_qualified_import", modules).unwrap_err();
assert_eq!(
err,
indoc!(
r#"
The package shorthand 'b' that you are using in the 'imports' section of the header of module 'tmp/module_interface_with_qualified_import/A.roc' doesn't exist.
Check that package shorthand is correct or reference the package in an 'app' or 'package' header.
This module is an interface, because of a bug in the compiler we are unable to directly typecheck interface modules with package imports so this error may not be correct. Please start checking at an app, package or platform file that imports this file."#
),
"\n{}",
err
);
}
#[test]
fn app_missing_package_import() {
let modules = vec![(
"main.roc",
indoc!(
r#"
app "example"
packages { pack: "./package/main.roc" }
imports [notpack.Mod]
provides [] to pack
main = ""
"#
),
)];
let err = multiple_modules("app_missing_package_import", modules).unwrap_err();
assert_eq!(
err,
indoc!(
r#"
The package shorthand 'notpack' that you are using in the 'imports' section of the header of module 'tmp/app_missing_package_import/main.roc' doesn't exist.
Check that package shorthand is correct or reference the package in an 'app' or 'package' header."#
),
"\n{}",
err
);
}
#[test]
fn non_roc_file_extension() {
let modules = vec![(
"main.md",
indoc!(
r"
# Not a roc file
"
),
)];
let expected = indoc!(
r"
NOT A ROC FILE in tmp/non_roc_file_extension/main.md
I expected a file with extension `.roc` or without extension.
Instead I received a file with extension `.md`."
);
let err = strip_colors(&multiple_modules("non_roc_file_extension", modules).unwrap_err());
assert_eq!(err, expected, "\n{}", err);
}
#[test]
fn roc_file_no_extension() {
let modules = vec![(
"main",
indoc!(
r#"
app "helloWorld"
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.8.1/x8URkvfyi9I0QhmVG98roKBUs_AZRkLFwFJVJ3942YA.tar.br" }
imports [pf.Stdout]
provides [main] to pf
main =
Stdout.line "Hello, World!"
"#
),
)];
let expected = indoc!(
r"
NOT A ROC FILE in tmp/roc_file_no_extension/main
I expected a file with either:
- extension `.roc`
- no extension and a roc shebang as the first line, e.g.
`#!/home/username/bin/roc_nightly/roc`
The provided file did not start with a shebang `#!` containing the
string `roc`. Is tmp/roc_file_no_extension/main a Roc file?"
);
let err = strip_colors(&multiple_modules("roc_file_no_extension", modules).unwrap_err());
assert_eq!(err, expected, "\n{}", err);
}

View file

@ -9,13 +9,11 @@ pub enum LowLevel {
StrJoinWith,
StrIsEmpty,
StrStartsWith,
StrStartsWithScalar,
StrEndsWith,
StrSplit,
StrCountGraphemes,
StrCountUtf8Bytes,
StrFromInt,
StrFromUtf8Range,
StrFromUtf8,
StrToUtf8,
StrRepeat,
StrFromFloat,
@ -23,17 +21,13 @@ pub enum LowLevel {
StrTrimStart,
StrTrimEnd,
StrToNum,
StrToScalars,
StrGetUnsafe,
StrSubstringUnsafe,
StrReserve,
StrAppendScalar,
StrGetScalarUnsafe,
StrGetCapacity,
StrWithCapacity,
StrGraphemes,
StrReleaseExcessCapacity,
ListLen,
ListLenUsize,
ListLenU64,
ListWithCapacity,
ListReserve,
ListReleaseExcessCapacity,
@ -50,8 +44,9 @@ pub enum LowLevel {
ListSublist,
ListDropAt,
ListSwap,
ListIsUnique,
ListGetCapacity,
ListIsUnique,
ListClone,
NumAdd,
NumAddWrap,
NumAddChecked,
@ -93,10 +88,6 @@ pub enum LowLevel {
NumAtan,
NumAcos,
NumAsin,
NumBytesToU16,
NumBytesToU32,
NumBytesToU64,
NumBytesToU128,
NumBitwiseAnd,
NumBitwiseXor,
NumBitwiseOr,
@ -263,34 +254,29 @@ map_symbol_to_lowlevel! {
StrJoinWith <= STR_JOIN_WITH;
StrIsEmpty <= STR_IS_EMPTY;
StrStartsWith <= STR_STARTS_WITH;
StrStartsWithScalar <= STR_STARTS_WITH_SCALAR;
StrEndsWith <= STR_ENDS_WITH;
StrSplit <= STR_SPLIT;
StrCountGraphemes <= STR_COUNT_GRAPHEMES;
StrCountUtf8Bytes <= STR_COUNT_UTF8_BYTES;
StrFromUtf8Range <= STR_FROM_UTF8_RANGE_LOWLEVEL;
StrFromUtf8 <= STR_FROM_UTF8_LOWLEVEL;
StrToUtf8 <= STR_TO_UTF8;
StrRepeat <= STR_REPEAT;
StrTrim <= STR_TRIM;
StrTrimStart <= STR_TRIM_START;
StrTrimEnd <= STR_TRIM_END;
StrToScalars <= STR_TO_SCALARS;
StrGetUnsafe <= STR_GET_UNSAFE;
StrSubstringUnsafe <= STR_SUBSTRING_UNSAFE;
StrReserve <= STR_RESERVE;
StrAppendScalar <= STR_APPEND_SCALAR_UNSAFE;
StrGetScalarUnsafe <= STR_GET_SCALAR_UNSAFE;
StrToNum <= STR_TO_NUM;
StrGetCapacity <= STR_CAPACITY;
StrWithCapacity <= STR_WITH_CAPACITY;
StrGraphemes <= STR_GRAPHEMES;
StrReleaseExcessCapacity <= STR_RELEASE_EXCESS_CAPACITY;
ListLen <= LIST_LEN;
ListLenU64 <= LIST_LEN_U64;
ListLenUsize <= LIST_LEN_USIZE;
ListGetCapacity <= LIST_CAPACITY;
ListWithCapacity <= LIST_WITH_CAPACITY;
ListReserve <= LIST_RESERVE;
ListReleaseExcessCapacity <= LIST_RELEASE_EXCESS_CAPACITY;
ListIsUnique <= LIST_IS_UNIQUE;
ListClone <= LIST_CLONE;
ListAppendUnsafe <= LIST_APPEND_UNSAFE;
ListPrepend <= LIST_PREPEND;
ListGetUnsafe <= LIST_GET_UNSAFE, DICT_LIST_GET_UNSAFE;
@ -318,8 +304,8 @@ map_symbol_to_lowlevel! {
NumCompare <= NUM_COMPARE;
NumDivFrac <= NUM_DIV_FRAC;
NumDivCeilUnchecked <= NUM_DIV_CEIL;
NumDivTruncUnchecked <= NUM_DIV_TRUNC;
NumRemUnchecked <= NUM_REM;
NumDivTruncUnchecked <= NUM_DIV_TRUNC_UNCHECKED;
NumRemUnchecked <= NUM_REM_UNCHECKED;
NumIsMultipleOf <= NUM_IS_MULTIPLE_OF;
NumAbs <= NUM_ABS;
NumNeg <= NUM_NEG;
@ -340,10 +326,6 @@ map_symbol_to_lowlevel! {
NumAtan <= NUM_ATAN;
NumAcos <= NUM_ACOS;
NumAsin <= NUM_ASIN;
NumBytesToU16 <= NUM_BYTES_TO_U16_LOWLEVEL;
NumBytesToU32 <= NUM_BYTES_TO_U32_LOWLEVEL;
NumBytesToU64 <= NUM_BYTES_TO_U64_LOWLEVEL;
NumBytesToU128 <= NUM_BYTES_TO_U128_LOWLEVEL;
NumBitwiseAnd <= NUM_BITWISE_AND;
NumBitwiseXor <= NUM_BITWISE_XOR;
NumBitwiseOr <= NUM_BITWISE_OR;

View file

@ -1192,19 +1192,19 @@ define_builtins! {
80 NUM_MUL_SATURATED: "mulSaturated"
81 NUM_INT: "Int" exposed_type=true
82 NUM_FRAC: "Frac" exposed_type=true
83 NUM_NATURAL: "Natural" exposed_type=true
84 NUM_NAT: "Nat" exposed_type=true
85 NUM_INT_CAST: "intCast"
83 NUM_E: "e"
84 NUM_PI: "pi"
85 NUM_TAU: "tau"
86 NUM_IS_MULTIPLE_OF: "isMultipleOf"
87 NUM_DECIMAL: "Decimal" exposed_type=true
88 NUM_DEC: "Dec" exposed_type=true // the Num.Dectype alias
89 NUM_BYTES_TO_U16: "bytesToU16"
90 NUM_BYTES_TO_U32: "bytesToU32"
91 NUM_BYTES_TO_U64: "bytesToU64"
92 NUM_BYTES_TO_U128: "bytesToU128"
93 NUM_CAST_TO_NAT: "#castToNat"
94 NUM_DIV_CEIL: "divCeil"
95 NUM_DIV_CEIL_CHECKED: "divCeilChecked"
89 NUM_COUNT_ONE_BITS: "countOneBits"
90 NUM_ABS_DIFF: "absDiff"
91 NUM_IS_NAN: "isNaN"
92 NUM_IS_INFINITE: "isInfinite"
93 NUM_IS_FINITE: "isFinite"
94 NUM_COUNT_LEADING_ZERO_BITS: "countLeadingZeroBits"
95 NUM_COUNT_TRAILING_ZERO_BITS: "countTrailingZeroBits"
96 NUM_TO_STR: "toStr"
97 NUM_MIN_I8: "minI8"
98 NUM_MAX_I8: "maxI8"
@ -1246,8 +1246,8 @@ define_builtins! {
134 NUM_TO_U64_CHECKED: "toU64Checked"
135 NUM_TO_U128: "toU128"
136 NUM_TO_U128_CHECKED: "toU128Checked"
137 NUM_TO_NAT: "toNat"
138 NUM_TO_NAT_CHECKED: "toNatChecked"
137 NUM_DIV_CEIL: "divCeil"
138 NUM_DIV_CEIL_CHECKED: "divCeilChecked"
139 NUM_TO_F32: "toF32"
140 NUM_TO_F32_CHECKED: "toF32Checked"
141 NUM_TO_F64: "toF64"
@ -1257,23 +1257,17 @@ define_builtins! {
145 NUM_ADD_CHECKED_LOWLEVEL: "addCheckedLowlevel"
146 NUM_SUB_CHECKED_LOWLEVEL: "subCheckedLowlevel"
147 NUM_MUL_CHECKED_LOWLEVEL: "mulCheckedLowlevel"
148 NUM_BYTES_TO_U16_LOWLEVEL: "bytesToU16Lowlevel"
149 NUM_BYTES_TO_U32_LOWLEVEL: "bytesToU32Lowlevel"
150 NUM_BYTES_TO_U64_LOWLEVEL: "bytesToU64Lowlevel"
151 NUM_BYTES_TO_U128_LOWLEVEL: "bytesToU128Lowlevel"
152 NUM_COUNT_LEADING_ZERO_BITS: "countLeadingZeroBits"
153 NUM_COUNT_TRAILING_ZERO_BITS: "countTrailingZeroBits"
154 NUM_COUNT_ONE_BITS: "countOneBits"
155 NUM_ABS_DIFF: "absDiff"
156 NUM_IS_NAN: "isNaN"
157 NUM_IS_INFINITE: "isInfinite"
158 NUM_IS_FINITE: "isFinite"
159 NUM_MIN: "min"
160 NUM_MAX: "max"
161 NUM_E: "e"
162 NUM_PI: "pi"
163 NUM_TAU: "tau"
164 NUM_BITWISE_NOT: "bitwiseNot"
148 NUM_MIN: "min"
149 NUM_MAX: "max"
150 NUM_BITWISE_NOT: "bitwiseNot"
151 NUM_INT_CAST: "intCast"
152 NUM_IS_APPROX_EQ: "isApproxEq"
153 NUM_BYTES_TO_U16_LOWLEVEL: "bytesToU16Lowlevel"
154 NUM_BYTES_TO_U32_LOWLEVEL: "bytesToU32Lowlevel"
155 NUM_BYTES_TO_U64_LOWLEVEL: "bytesToU64Lowlevel"
156 NUM_BYTES_TO_U128_LOWLEVEL: "bytesToU128Lowlevel"
157 NUM_DIV_TRUNC_UNCHECKED: "divTruncUnchecked" // traps on division by zero
158 NUM_REM_UNCHECKED: "remUnchecked" // traps on division by zero
}
4 BOOL: "Bool" => {
0 BOOL_BOOL: "Bool" exposed_type=true // the Bool.Bool type alias
@ -1297,24 +1291,24 @@ define_builtins! {
3 STR_CONCAT: "concat"
4 STR_JOIN_WITH: "joinWith"
5 STR_SPLIT: "split"
6 STR_COUNT_GRAPHEMES: "countGraphemes"
6 STR_WITH_PREFIX: "withPrefix"
7 STR_STARTS_WITH: "startsWith"
8 STR_ENDS_WITH: "endsWith"
9 STR_FROM_UTF8: "fromUtf8"
10 STR_UT8_PROBLEM: "Utf8Problem" // the Utf8Problem type alias
11 STR_UT8_BYTE_PROBLEM: "Utf8ByteProblem" // the Utf8ByteProblem type alias
12 STR_TO_UTF8: "toUtf8"
13 STR_STARTS_WITH_SCALAR: "startsWithScalar"
13 STR_WALK_UTF8: "walkUtf8"
14 STR_ALIAS_ANALYSIS_STATIC: "#aliasAnalysisStatic" // string with the static lifetime
15 STR_FROM_UTF8_RANGE: "fromUtf8Range"
16 STR_REPEAT: "repeat"
17 STR_TRIM: "trim"
18 STR_TRIM_START: "trimStart"
19 STR_TRIM_END: "trimEnd"
20 STR_TO_DEC: "toDec"
20 STR_WITH_CAPACITY: "withCapacity"
21 STR_TO_F64: "toF64"
22 STR_TO_F32: "toF32"
23 STR_TO_NAT: "toNat"
23 STR_TO_DEC: "toDec"
24 STR_TO_U128: "toU128"
25 STR_TO_I128: "toI128"
26 STR_TO_U64: "toU64"
@ -1325,7 +1319,7 @@ define_builtins! {
31 STR_TO_I16: "toI16"
32 STR_TO_U8: "toU8"
33 STR_TO_I8: "toI8"
34 STR_TO_SCALARS: "toScalars"
34 STR_CONTAINS: "contains"
35 STR_GET_UNSAFE: "getUnsafe"
36 STR_COUNT_UTF8_BYTES: "countUtf8Bytes"
37 STR_SUBSTRING_UNSAFE: "substringUnsafe"
@ -1333,24 +1327,13 @@ define_builtins! {
39 STR_SPLIT_LAST: "splitLast"
40 STR_WALK_UTF8_WITH_INDEX: "walkUtf8WithIndex"
41 STR_RESERVE: "reserve"
42 STR_APPEND_SCALAR_UNSAFE: "appendScalarUnsafe"
43 STR_APPEND_SCALAR: "appendScalar"
44 STR_GET_SCALAR_UNSAFE: "getScalarUnsafe"
45 STR_WALK_SCALARS: "walkScalars"
46 STR_WALK_SCALARS_UNTIL: "walkScalarsUntil"
47 STR_TO_NUM: "strToNum"
48 STR_FROM_UTF8_RANGE_LOWLEVEL: "fromUtf8RangeLowlevel"
49 STR_CAPACITY: "capacity"
50 STR_REPLACE_EACH: "replaceEach"
51 STR_REPLACE_FIRST: "replaceFirst"
52 STR_REPLACE_LAST: "replaceLast"
53 STR_WITH_CAPACITY: "withCapacity"
54 STR_WITH_PREFIX: "withPrefix"
55 STR_GRAPHEMES: "graphemes"
56 STR_IS_VALID_SCALAR: "isValidScalar"
57 STR_RELEASE_EXCESS_CAPACITY: "releaseExcessCapacity"
58 STR_WALK_UTF8: "walkUtf8"
59 STR_CONTAINS: "contains"
42 STR_TO_NUM: "strToNum"
43 STR_FROM_UTF8_LOWLEVEL: "fromUtf8Lowlevel"
44 STR_CAPACITY: "capacity"
45 STR_REPLACE_EACH: "replaceEach"
46 STR_REPLACE_FIRST: "replaceFirst"
47 STR_REPLACE_LAST: "replaceLast"
48 STR_RELEASE_EXCESS_CAPACITY: "releaseExcessCapacity"
}
6 LIST: "List" => {
0 LIST_LIST: "List" exposed_apply_type=true // the List.List type alias
@ -1359,7 +1342,7 @@ define_builtins! {
3 LIST_SET: "set"
4 LIST_APPEND: "append"
5 LIST_MAP: "map"
6 LIST_LEN: "len"
6 LIST_LEN_U64: "len"
7 LIST_WALK_BACKWARDS: "walkBackwards"
8 LIST_CONCAT: "concat"
9 LIST_FIRST: "first"
@ -1440,6 +1423,8 @@ define_builtins! {
84 LIST_APPEND_IF_OK: "appendIfOk"
85 LIST_PREPEND_IF_OK: "prependIfOk"
86 LIST_WALK_WITH_INDEX_UNTIL: "walkWithIndexUntil"
87 LIST_CLONE: "clone"
88 LIST_LEN_USIZE: "lenUsize"
}
7 RESULT: "Result" => {
0 RESULT_RESULT: "Result" exposed_type=true // the Result.Result type alias
@ -1598,13 +1583,12 @@ define_builtins! {
12 HASH_HASH_I32: "hashI32"
13 HASH_HASH_I64: "hashI64"
14 HASH_HASH_I128: "hashI128"
15 HASH_HASH_NAT: "hashNat"
15 HASH_HASH_UNORDERED: "hashUnordered"
16 I128_OF_DEC: "i128OfDec"
17 HASH_HASH_DEC: "hashDec"
18 HASH_COMPLETE: "complete"
19 HASH_HASH_STR_BYTES: "hashStrBytes"
20 HASH_HASH_LIST: "hashList"
21 HASH_HASH_UNORDERED: "hashUnordered"
}
14 INSPECT: "Inspect" => {
0 INSPECT_INSPECT_ABILITY: "Inspect" exposed_type=true
@ -1640,8 +1624,7 @@ define_builtins! {
30 INSPECT_CUSTOM: "custom"
31 INSPECT_APPLY: "apply"
32 INSPECT_TO_INSPECTOR: "toInspector"
33 INSPECT_NAT: "nat"
34 INSPECT_TO_STR: "toStr"
33 INSPECT_TO_STR: "toStr"
}
15 JSON: "TotallyNotJson" => {
0 JSON_JSON: "TotallyNotJson"

View file

@ -668,8 +668,8 @@ fn eq_list<'a>(
let len_1 = root.create_symbol(ident_ids, "len_1");
let len_2 = root.create_symbol(ident_ids, "len_2");
let len_1_stmt = |next| let_lowlevel(arena, layout_isize, len_1, ListLen, &[ARG_1], next);
let len_2_stmt = |next| let_lowlevel(arena, layout_isize, len_2, ListLen, &[ARG_2], next);
let len_1_stmt = |next| let_lowlevel(arena, layout_isize, len_1, ListLenUsize, &[ARG_1], next);
let len_2_stmt = |next| let_lowlevel(arena, layout_isize, len_2, ListLenUsize, &[ARG_2], next);
let eq_len = root.create_symbol(ident_ids, "eq_len");
let eq_len_stmt = |next| let_lowlevel(arena, LAYOUT_BOOL, eq_len, Eq, &[len_1, len_2], next);

View file

@ -926,7 +926,7 @@ fn refcount_list<'a>(
//
let len = root.create_symbol(ident_ids, "len");
let len_stmt = |next| let_lowlevel(arena, layout_isize, len, ListLen, &[structure], next);
let len_stmt = |next| let_lowlevel(arena, layout_isize, len, ListLenUsize, &[structure], next);
// let zero = 0
let zero = root.create_symbol(ident_ids, "zero");

View file

@ -1533,17 +1533,14 @@ fn low_level_no_rc(lowlevel: &LowLevel) -> RC {
match lowlevel {
Unreachable => RC::Uknown,
ListLen | StrIsEmpty | StrToScalars | StrCountGraphemes | StrGraphemes
| StrCountUtf8Bytes | StrGetCapacity | ListGetCapacity => RC::NoRc,
ListWithCapacity | StrWithCapacity => RC::NoRc,
ListLenU64 | ListLenUsize | StrIsEmpty | StrCountUtf8Bytes | ListGetCapacity
| ListWithCapacity | StrWithCapacity => RC::NoRc,
ListReplaceUnsafe => RC::Rc,
StrGetUnsafe | ListGetUnsafe => RC::NoRc,
ListConcat => RC::Rc,
StrConcat => RC::Rc,
StrSubstringUnsafe => RC::Rc,
StrReserve => RC::Rc,
StrAppendScalar => RC::Rc,
StrGetScalarUnsafe => RC::NoRc,
StrTrim => RC::Rc,
StrTrimStart => RC::Rc,
StrTrimEnd => RC::Rc,
@ -1596,21 +1593,17 @@ fn low_level_no_rc(lowlevel: &LowLevel) -> RC {
| NumCountLeadingZeroBits
| NumCountTrailingZeroBits
| NumCountOneBits => RC::NoRc,
NumBytesToU16 => RC::NoRc,
NumBytesToU32 => RC::NoRc,
NumBytesToU64 => RC::NoRc,
NumBytesToU128 => RC::NoRc,
I128OfDec => RC::NoRc,
DictPseudoSeed => RC::NoRc,
StrStartsWith | StrEndsWith => RC::NoRc,
StrStartsWithScalar => RC::NoRc,
StrFromUtf8Range => RC::Rc,
StrFromUtf8 => RC::Rc,
StrToUtf8 => RC::Rc,
StrRepeat => RC::NoRc,
StrFromInt | StrFromFloat => RC::NoRc,
Hash => RC::NoRc,
ListIsUnique => RC::Rc,
ListClone => RC::Rc,
BoxExpr | UnboxExpr => {
unreachable!("These lowlevel operations are turned into mono Expr's")

View file

@ -1283,8 +1283,7 @@ fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[Ownership] {
match op {
Unreachable => arena.alloc_slice_copy(&[irrelevant]),
DictPseudoSeed => arena.alloc_slice_copy(&[irrelevant]),
ListLen | StrIsEmpty | StrToScalars | StrCountGraphemes | StrGraphemes
| StrCountUtf8Bytes | StrGetCapacity | ListGetCapacity => {
ListLenU64 | ListLenUsize | StrIsEmpty | StrCountUtf8Bytes | ListGetCapacity => {
arena.alloc_slice_copy(&[borrowed])
}
ListWithCapacity | StrWithCapacity => arena.alloc_slice_copy(&[irrelevant]),
@ -1294,8 +1293,6 @@ fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[Ownership] {
StrConcat => arena.alloc_slice_copy(&[owned, borrowed]),
StrSubstringUnsafe => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),
StrReserve => arena.alloc_slice_copy(&[owned, irrelevant]),
StrAppendScalar => arena.alloc_slice_copy(&[owned, irrelevant]),
StrGetScalarUnsafe => arena.alloc_slice_copy(&[borrowed, irrelevant]),
StrTrim => arena.alloc_slice_copy(&[owned]),
StrTrimStart => arena.alloc_slice_copy(&[owned]),
StrTrimEnd => arena.alloc_slice_copy(&[owned]),
@ -1308,7 +1305,6 @@ fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[Ownership] {
ListMap3 => arena.alloc_slice_copy(&[owned, owned, owned, function, closure_data]),
ListMap4 => arena.alloc_slice_copy(&[owned, owned, owned, owned, function, closure_data]),
ListSortWith => arena.alloc_slice_copy(&[owned, function, closure_data]),
ListAppendUnsafe => arena.alloc_slice_copy(&[owned, owned]),
ListReserve => arena.alloc_slice_copy(&[owned, irrelevant]),
ListSublist => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),
@ -1353,19 +1349,15 @@ fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[Ownership] {
| NumCountTrailingZeroBits
| NumCountOneBits
| I128OfDec => arena.alloc_slice_copy(&[irrelevant]),
NumBytesToU16 => arena.alloc_slice_copy(&[borrowed, irrelevant]),
NumBytesToU32 => arena.alloc_slice_copy(&[borrowed, irrelevant]),
NumBytesToU64 => arena.alloc_slice_copy(&[borrowed, irrelevant]),
NumBytesToU128 => arena.alloc_slice_copy(&[borrowed, irrelevant]),
StrStartsWith | StrEndsWith => arena.alloc_slice_copy(&[borrowed, borrowed]),
StrStartsWithScalar => arena.alloc_slice_copy(&[borrowed, irrelevant]),
StrFromUtf8Range => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),
StrFromUtf8 => arena.alloc_slice_copy(&[owned]),
StrToUtf8 => arena.alloc_slice_copy(&[owned]),
StrRepeat => arena.alloc_slice_copy(&[borrowed, irrelevant]),
StrFromInt | StrFromFloat => arena.alloc_slice_copy(&[irrelevant]),
Hash => arena.alloc_slice_copy(&[borrowed, irrelevant]),
ListIsUnique => arena.alloc_slice_copy(&[borrowed]),
ListClone => arena.alloc_slice_copy(&[owned]),
BoxExpr | UnboxExpr => {
unreachable!("These lowlevel operations are turned into mono Expr's")

View file

@ -818,7 +818,7 @@ type NumberSpecializations<'a> = VecMap<InLayout<'a>, (Symbol, UseDepth)>;
/// n = 1
/// use1 : U8
/// use1 = 1
/// use2 : Nat
/// use2 : Dec
/// use2 = 2
///
/// We keep track of the specializations of `myTag` and create fresh symbols when there is more
@ -1032,9 +1032,7 @@ impl<'a> Procs<'a> {
ret_var: Variable,
layout_cache: &mut LayoutCache<'a>,
) -> Result<ProcLayout<'a>, RuntimeError> {
let raw_layout = layout_cache
.raw_from_var(env.arena, annotation, env.subs)
.unwrap_or_else(|err| panic!("TODO turn fn_var into a RuntimeError {err:?}"));
let raw_layout = layout_cache.raw_from_var(env.arena, annotation, env.subs)?;
let top_level = ProcLayout::from_raw_named(env.arena, name, raw_layout);
@ -3120,8 +3118,7 @@ fn specialize_host_specializations<'a>(
let symbol = env.unique_symbol();
let lambda_name = LambdaName::no_niche(symbol);
let mut layout_env =
layout::Env::from_components(layout_cache, env.subs, env.arena, env.target_info);
let mut layout_env = layout::Env::from_components(layout_cache, env.subs, env.arena);
let lambda_set = env.subs.get_lambda_set(var);
let raw_function_layout =
RawFunctionLayout::from_var(&mut layout_env, lambda_set.ambient_function)
@ -4542,12 +4539,8 @@ pub fn with_hole<'a>(
tuple_var, elems, ..
} => {
let sorted_elems_result = {
let mut layout_env = layout::Env::from_components(
layout_cache,
env.subs,
env.arena,
env.target_info,
);
let mut layout_env =
layout::Env::from_components(layout_cache, env.subs, env.arena);
layout::sort_tuple_elems(&mut layout_env, tuple_var)
};
let sorted_elems = match sorted_elems_result {
@ -4577,12 +4570,8 @@ pub fn with_hole<'a>(
..
} => {
let sorted_fields_result = {
let mut layout_env = layout::Env::from_components(
layout_cache,
env.subs,
env.arena,
env.target_info,
);
let mut layout_env =
layout::Env::from_components(layout_cache, env.subs, env.arena);
layout::sort_record_fields(&mut layout_env, record_var)
};
let sorted_fields = match sorted_fields_result {
@ -4864,9 +4853,10 @@ pub fn with_hole<'a>(
let mut symbol_exprs = Vec::with_capacity_in(loc_elems.len(), env.arena);
let elem_layout = layout_cache
.from_var(env.arena, elem_var, env.subs)
.unwrap_or_else(|err| panic!("TODO turn fn_var into a RuntimeError {err:?}"));
let elem_layout = match layout_cache.from_var(env.arena, elem_var, env.subs) {
Ok(elem_layout) => elem_layout,
Err(_) => return runtime_error(env, "invalid list element type"),
};
for arg_expr in loc_elems.into_iter() {
if let Some(literal) =
@ -4916,12 +4906,8 @@ pub fn with_hole<'a>(
..
} => {
let sorted_fields_result = {
let mut layout_env = layout::Env::from_components(
layout_cache,
env.subs,
env.arena,
env.target_info,
);
let mut layout_env =
layout::Env::from_components(layout_cache, env.subs, env.arena);
layout::sort_record_fields(&mut layout_env, record_var)
};
let sorted_fields = match sorted_fields_result {
@ -5029,12 +5015,8 @@ pub fn with_hole<'a>(
..
} => {
let sorted_elems_result = {
let mut layout_env = layout::Env::from_components(
layout_cache,
env.subs,
env.arena,
env.target_info,
);
let mut layout_env =
layout::Env::from_components(layout_cache, env.subs, env.arena);
layout::sort_tuple_elems(&mut layout_env, tuple_var)
};
let sorted_elems = match sorted_elems_result {
@ -5142,12 +5124,8 @@ pub fn with_hole<'a>(
// This has the benefit that we don't need to do anything special for reference
// counting
let sorted_fields_result = {
let mut layout_env = layout::Env::from_components(
layout_cache,
env.subs,
env.arena,
env.target_info,
);
let mut layout_env =
layout::Env::from_components(layout_cache, env.subs, env.arena);
layout::sort_record_fields(&mut layout_env, record_var)
};
@ -5156,6 +5134,21 @@ pub fn with_hole<'a>(
Err(_) => return runtime_error(env, "Can't update record with improper layout"),
};
let sorted_fields_filtered =
sorted_fields
.iter()
.filter_map(|(label, _, opt_field_layout)| {
match opt_field_layout {
Ok(_) => Some(label),
Err(_) => {
debug_assert!(!updates.contains_key(label));
// this was an optional field, and now does not exist!
None
}
}
});
let sorted_fields = Vec::from_iter_in(sorted_fields_filtered, env.arena);
let single_field_struct = sorted_fields.len() == 1;
// The struct indexing generated by the current context
@ -5164,44 +5157,38 @@ pub fn with_hole<'a>(
let mut new_struct_symbols = Vec::with_capacity_in(sorted_fields.len(), env.arena);
// Information about the fields that are being updated
let mut fields = Vec::with_capacity_in(sorted_fields.len(), env.arena);
let mut index = 0;
for (label, _, opt_field_layout) in sorted_fields.iter() {
let record_index = (structure, index);
match opt_field_layout {
Err(_) => {
debug_assert!(!updates.contains_key(label));
// this was an optional field, and now does not exist!
// do not increment `index`!
}
Ok(_field_layout) => {
current_struct_indexing.push(record_index);
// Create a symbol for each of the fields as they might be referenced later.
// The struct with a single field is optimized in such a way that replacing later indexing will cause an incorrect IR.
// Thus, only insert these struct_indices if there is more than one field in the struct.
if !single_field_struct {
for index in 0..sorted_fields.len() {
let record_index = (structure, index as u64);
// The struct with a single field is optimized in such a way that replacing later indexing will cause an incorrect IR.
// Thus, only insert these struct_indices if there is more than one field in the struct.
if !single_field_struct {
let original_struct_symbol = env.unique_symbol();
env.struct_indexing
.insert(record_index, original_struct_symbol);
}
if let Some(field) = updates.get(label) {
let new_struct_symbol = possible_reuse_symbol_or_specialize(
env,
procs,
layout_cache,
&field.loc_expr.value,
field.var,
);
new_struct_symbols.push(new_struct_symbol);
fields.push(UpdateExisting(field));
} else {
new_struct_symbols
.push(*env.struct_indexing.get(record_index).unwrap());
fields.push(CopyExisting);
}
current_struct_indexing.push(record_index);
index += 1;
}
let original_struct_symbol = env.unique_symbol();
env.struct_indexing
.insert(record_index, original_struct_symbol);
}
}
for (index, label) in sorted_fields.iter().enumerate() {
let record_index = (structure, index as u64);
if let Some(field) = updates.get(label) {
let new_struct_symbol = possible_reuse_symbol_or_specialize(
env,
procs,
layout_cache,
&field.loc_expr.value,
field.var,
);
new_struct_symbols.push(new_struct_symbol);
fields.push(UpdateExisting(field));
} else {
new_struct_symbols.push(*env.struct_indexing.get(record_index).unwrap());
fields.push(CopyExisting);
}
}
@ -6368,8 +6355,7 @@ fn convert_tag_union<'a>(
) -> Stmt<'a> {
use crate::layout::UnionVariant::*;
let res_variant = {
let mut layout_env =
layout::Env::from_components(layout_cache, env.subs, env.arena, env.target_info);
let mut layout_env = layout::Env::from_components(layout_cache, env.subs, env.arena);
crate::layout::union_sorted_tags(&mut layout_env, variant_var)
};
let variant = match res_variant {
@ -6867,7 +6853,6 @@ fn register_capturing_closure<'a>(
args,
closure_var,
ret,
env.target_info,
)
};
@ -8046,12 +8031,8 @@ fn can_reuse_symbol<'a>(
..
} => {
let sorted_fields_result = {
let mut layout_env = layout::Env::from_components(
layout_cache,
env.subs,
env.arena,
env.target_info,
);
let mut layout_env =
layout::Env::from_components(layout_cache, env.subs, env.arena);
layout::sort_record_fields(&mut layout_env, *record_var)
};

View file

@ -1769,7 +1769,7 @@ fn test_to_comparison<'a>(
LayoutRepr::Builtin(Builtin::List(_elem_layout)) => {
let real_len_expr = Expr::Call(Call {
call_type: CallType::LowLevel {
op: LowLevel::ListLen,
op: LowLevel::ListLenUsize,
update_mode: env.next_update_mode_id(),
},
arguments: env.arena.alloc([list_sym]),
@ -2346,7 +2346,7 @@ fn decide_to_branching<'a>(
let len_expr = Expr::Call(Call {
call_type: CallType::LowLevel {
op: LowLevel::ListLen,
op: LowLevel::ListLenUsize,
update_mode: env.next_update_mode_id(),
},
arguments: env.arena.alloc([inner_cond_symbol]),

View file

@ -385,12 +385,8 @@ fn from_can_pattern_help<'a>(
use roc_exhaustive::Union;
let res_variant = {
let mut layout_env = layout::Env::from_components(
layout_cache,
env.subs,
env.arena,
env.target_info,
);
let mut layout_env =
layout::Env::from_components(layout_cache, env.subs, env.arena);
crate::layout::union_sorted_tags(&mut layout_env, *whole_var).map_err(Into::into)
};
@ -880,12 +876,8 @@ fn from_can_pattern_help<'a>(
} => {
// sorted fields based on the type
let sorted_elems = {
let mut layout_env = layout::Env::from_components(
layout_cache,
env.subs,
env.arena,
env.target_info,
);
let mut layout_env =
layout::Env::from_components(layout_cache, env.subs, env.arena);
crate::layout::sort_tuple_elems(&mut layout_env, *whole_var)
.map_err(RuntimeError::from)?
};
@ -936,12 +928,8 @@ fn from_can_pattern_help<'a>(
} => {
// sorted fields based on the type
let sorted_fields = {
let mut layout_env = layout::Env::from_components(
layout_cache,
env.subs,
env.arena,
env.target_info,
);
let mut layout_env =
layout::Env::from_components(layout_cache, env.subs, env.arena);
crate::layout::sort_record_fields(&mut layout_env, *whole_var)
.map_err(RuntimeError::from)?
};
@ -1409,22 +1397,20 @@ pub(crate) fn build_list_index_probe<'a>(
list_sym: Symbol,
list_index: &ListIndex,
) -> (Symbol, impl DoubleEndedIterator<Item = Store<'a>>) {
let usize_layout = Layout::usize(env.target_info);
let list_index = list_index.0;
let index_sym = env.unique_symbol();
let (opt_len_store, opt_offset_store, index_store) = if list_index >= 0 {
let index_expr = Expr::Literal(Literal::Int((list_index as i128).to_ne_bytes()));
let index_store = (index_sym, usize_layout, index_expr);
let index_store = (index_sym, Layout::U64, index_expr);
(None, None, index_store)
} else {
let len_sym = env.unique_symbol();
let len_expr = Expr::Call(Call {
call_type: CallType::LowLevel {
op: LowLevel::ListLen,
op: LowLevel::ListLenU64,
update_mode: env.next_update_mode_id(),
},
arguments: env.arena.alloc([list_sym]),
@ -1442,9 +1428,9 @@ pub(crate) fn build_list_index_probe<'a>(
arguments: env.arena.alloc([len_sym, offset_sym]),
});
let len_store = (len_sym, usize_layout, len_expr);
let offset_store = (offset_sym, usize_layout, offset_expr);
let index_store = (index_sym, usize_layout, index_expr);
let len_store = (len_sym, Layout::U64, len_expr);
let offset_store = (offset_sym, Layout::U64, offset_expr);
let index_store = (index_sym, Layout::U64, index_expr);
(Some(len_store), Some(offset_store), index_store)
};
@ -1572,17 +1558,16 @@ fn store_list_rest<'a>(
if let Some((index, Some(rest_sym))) = opt_rest {
is_productive = true;
let usize_layout = Layout::usize(env.target_info);
let total_dropped = list_arity.min_len();
let total_dropped_sym = env.unique_symbol();
let total_dropped_expr = Expr::Literal(Literal::Int((total_dropped as u128).to_ne_bytes()));
let list_len_sym = env.unique_symbol();
let list_len_expr = Expr::Call(Call {
call_type: CallType::LowLevel {
op: LowLevel::ListLen,
// Must use ListLenU64 here because we're using it with List.sublist,
// which takes U64s for start and len.
op: LowLevel::ListLenU64,
update_mode: env.next_update_mode_id(),
},
arguments: env.arena.alloc([list_sym]),
@ -1608,10 +1593,10 @@ fn store_list_rest<'a>(
arguments: env.arena.alloc([list_sym, start_sym, rest_len_sym]),
});
let needed_stores = [
(total_dropped_sym, total_dropped_expr, usize_layout),
(list_len_sym, list_len_expr, usize_layout),
(rest_len_sym, rest_len_expr, usize_layout),
(start_sym, start_expr, usize_layout),
(total_dropped_sym, total_dropped_expr, Layout::U64),
(list_len_sym, list_len_expr, Layout::U64),
(rest_len_sym, rest_len_expr, Layout::U64),
(start_sym, start_expr, Layout::U64),
(*rest_sym, rest_expr, list_layout),
];
for (sym, expr, lay) in needed_stores.into_iter().rev() {

View file

@ -160,7 +160,6 @@ impl<'a> LayoutCache<'a> {
arena,
subs,
seen: Vec::new_in(arena),
target_info: self.target_info,
cache: self,
};
@ -186,7 +185,6 @@ impl<'a> LayoutCache<'a> {
arena,
subs,
seen: Vec::new_in(arena),
target_info: self.target_info,
cache: self,
};
@ -574,12 +572,6 @@ impl<'a> RawFunctionLayout<'a> {
cacheable(Ok(Self::ZeroArgumentThunk(Layout::F32)))
}
// Nat
Alias(Symbol::NUM_NAT, args, _, _) => {
debug_assert!(args.is_empty());
cacheable(Ok(Self::ZeroArgumentThunk(Layout::usize(env.target_info))))
}
Alias(Symbol::INSPECT_ELEM_WALKER | Symbol::INSPECT_KEY_VAL_WALKER, _, var, _) => Self::from_var(env, var),
Alias(symbol, _, var, _) if symbol.is_builtin() => {
@ -1825,9 +1817,8 @@ impl<'a> LambdaSet<'a> {
args: VariableSubsSlice,
closure_var: Variable,
ret_var: Variable,
target_info: TargetInfo,
) -> Result<Self, LayoutProblem> {
let mut env = Env::from_components(cache, subs, arena, target_info);
let mut env = Env::from_components(cache, subs, arena);
Self::from_var(&mut env, args, closure_var, ret_var).value()
}
@ -2235,7 +2226,6 @@ macro_rules! list_element_layout {
}
pub struct Env<'a, 'b> {
target_info: TargetInfo,
pub(crate) arena: &'a Bump,
seen: Vec<'a, Variable>,
pub(crate) subs: &'b Subs,
@ -2247,14 +2237,12 @@ impl<'a, 'b> Env<'a, 'b> {
cache: &'b mut LayoutCache<'a>,
subs: &'b Subs,
arena: &'a Bump,
target_info: TargetInfo,
) -> Self {
Self {
cache,
subs,
seen: Vec::new_in(arena),
arena,
target_info,
}
}
@ -2505,10 +2493,6 @@ impl<'a> Layout<'a> {
match symbol {
Symbol::NUM_DECIMAL => cacheable(Ok(Layout::DEC)),
Symbol::NUM_NAT | Symbol::NUM_NATURAL => {
cacheable(Ok(Layout::usize(env.target_info)))
}
Symbol::NUM_NUM | Symbol::NUM_INT | Symbol::NUM_INTEGER
if is_unresolved_var(env.subs, actual_var) =>
{
@ -2528,7 +2512,7 @@ impl<'a> Layout<'a> {
}
}
RangedNumber(range) => Self::layout_from_ranged_number(env, range),
RangedNumber(range) => Self::layout_from_ranged_number(range),
Error => cacheable(Err(LayoutProblem::Erroneous)),
}
@ -2549,18 +2533,12 @@ impl<'a> Layout<'a> {
}
}
fn layout_from_ranged_number(
env: &mut Env<'a, '_>,
range: NumericRange,
) -> Cacheable<LayoutResult<'a>> {
fn layout_from_ranged_number(range: NumericRange) -> Cacheable<LayoutResult<'a>> {
// We don't pass the range down because `RangedNumber`s are somewhat rare, they only
// appear due to number literals, so no need to increase parameter list sizes.
let num_layout = range.default_compilation_width();
cacheable(Ok(Layout::int_literal_width_to_int(
num_layout,
env.target_info,
)))
cacheable(Ok(Layout::int_literal_width_to_int(num_layout)))
}
/// Returns Err(()) if given an error, or Ok(Layout) if given a non-erroneous Structure.
@ -3038,10 +3016,7 @@ impl<'a> Layout<'a> {
Layout::DEC
}
pub fn int_literal_width_to_int(
width: roc_types::num::IntLitWidth,
target_info: TargetInfo,
) -> InLayout<'a> {
pub fn int_literal_width_to_int(width: roc_types::num::IntLitWidth) -> InLayout<'a> {
use roc_types::num::IntLitWidth::*;
match width {
U8 => Layout::U8,
@ -3054,7 +3029,6 @@ impl<'a> Layout<'a> {
I32 => Layout::I32,
I64 => Layout::I64,
I128 => Layout::I128,
Nat => Layout::usize(target_info),
// f32 int literal bounded by +/- 2^24, so fit it into an i32
F32 => Layout::F32,
// f64 int literal bounded by +/- 2^53, so fit it into an i32
@ -3240,7 +3214,6 @@ fn layout_from_flat_type<'a>(
let arena = env.arena;
let subs = env.subs;
let target_info = env.target_info;
match flat_type {
Apply(symbol, args) => {
@ -3248,11 +3221,6 @@ fn layout_from_flat_type<'a>(
match symbol {
// Ints
Symbol::NUM_NAT => {
debug_assert_eq!(args.len(), 0);
cacheable(Ok(Layout::usize(env.target_info)))
}
Symbol::NUM_I128 => {
debug_assert_eq!(args.len(), 0);
cacheable(Ok(Layout::I128))
@ -3316,7 +3284,7 @@ fn layout_from_flat_type<'a>(
let var = args[0];
let content = subs.get_content_without_compacting(var);
layout_from_num_content(content, target_info)
layout_from_num_content(content)
}
Symbol::STR_STR => cacheable(Ok(Layout::STR)),
@ -4550,10 +4518,7 @@ pub fn ext_var_is_empty_tag_union(_: &Subs, _: TagExt) -> bool {
unreachable!();
}
fn layout_from_num_content<'a>(
content: &Content,
target_info: TargetInfo,
) -> Cacheable<LayoutResult<'a>> {
fn layout_from_num_content<'a>(content: &Content) -> Cacheable<LayoutResult<'a>> {
use roc_types::subs::Content::*;
use roc_types::subs::FlatType::*;
@ -4569,8 +4534,6 @@ fn layout_from_num_content<'a>(
FlexAbleVar(_, _) | RigidAbleVar(_, _) => todo_abilities!("Not reachable yet"),
Structure(Apply(symbol, args)) => match *symbol {
// Ints
Symbol::NUM_NAT => Ok(Layout::usize(target_info)),
Symbol::NUM_INTEGER => Ok(Layout::I64),
Symbol::NUM_I128 => Ok(Layout::I128),
Symbol::NUM_I64 => Ok(Layout::I64),

View file

@ -103,6 +103,13 @@ macro_rules! impl_to_from_int_width {
_ => roc_error_macros::internal_error!("not an integer layout!")
}
}
pub fn try_to_int_width(&self) -> Option<IntWidth> {
match self {
$(&$layout => Some($int_width),)*
_ => None,
}
}
}
};
}

View file

@ -61,17 +61,16 @@ enum FirstOrder {
StrJoinWith,
StrIsEmpty,
StrStartsWith,
StrStartsWithScalar,
StrEndsWith,
StrSplit,
StrCountGraphemes,
StrFromInt,
StrFromUtf8,
StrFromUtf8Range,
StrToUtf8,
StrRepeat,
StrFromFloat,
ListLen,
ListLenU64,
ListLenUsize,
ListGetUnsafe,
ListSublist,
ListDropAt,
@ -121,10 +120,6 @@ enum FirstOrder {
NumBitwiseOr,
NumShiftLeftBy,
NumShiftRightBy,
NumBytesToU16,
NumBytesToU32,
NumBytesToU64,
NumBytesToU128,
NumShiftRightZfBy,
NumIntCast,
NumFloatCast,

View file

@ -569,7 +569,7 @@ fn trmc_candidates_help(
// ```roc
// LinkedList a : [ Nil, Cons a (LinkedList a) ]
//
// repeat : a, Nat -> LinkedList a
// repeat : a, U64 -> LinkedList a
// repeat = \element, n ->
// when n is
// 0 -> Nil
@ -581,7 +581,7 @@ fn trmc_candidates_help(
// But there is a trick: TRMC. Using TRMC and join points, we are able to convert this function into a loop, which uses only one stack frame for the whole process.
//
// ```pseudo-roc
// repeat : a, Nat -> LinkedList a
// repeat : a, U64 -> LinkedList a
// repeat = \initialElement, initialN ->
// joinpoint trmc = \element, n, hole, head ->
// when n is

View file

@ -1296,6 +1296,16 @@ impl<'a> Spaceable<'a> for ImplementsAbilities<'a> {
}
impl<'a> Expr<'a> {
pub const REPL_OPAQUE_FUNCTION: Self = Expr::Var {
module_name: "",
ident: "<function>",
};
pub const REPL_RUNTIME_CRASH: Self = Expr::Var {
module_name: "",
ident: "*",
};
pub fn loc_ref(&'a self, region: Region) -> Loc<&'a Self> {
Loc {
region,

View file

@ -23,6 +23,16 @@ impl<'a> HeaderType<'a> {
HeaderType::Platform { .. } | HeaderType::Package { .. } => &[],
}
}
pub fn to_string(&'a self) -> &str {
match self {
HeaderType::App { .. } => "app",
HeaderType::Hosted { .. } => "hosted",
HeaderType::Builtin { .. } => "builtin",
HeaderType::Package { .. } => "package",
HeaderType::Platform { .. } => "platform",
HeaderType::Interface { .. } => "interface",
}
}
}
#[derive(Debug)]

View file

@ -173,6 +173,8 @@ pub fn parse_str_like_literal<'a>() -> impl Parser<'a, StrLikeLiteral<'a>, EStri
};
}
let mut preceded_by_dollar = false;
while let Some(&byte) = bytes.next() {
// This is for the byte we just grabbed from the iterator.
segment_parsed_bytes += 1;
@ -349,68 +351,6 @@ pub fn parse_str_like_literal<'a>() -> impl Parser<'a, StrLikeLiteral<'a>, EStri
return Err((MadeProgress, EString::EndlessSingleLine(start_state.pos())));
}
}
b'$' => {
// This is for the byte we're about to parse.
segment_parsed_bytes += 1;
// iff the '$' is followed by '(', this is string interpolation.
if let Some(b'(') = bytes.next() {
// We're about to begin string interpolation!
//
// End the previous segment so we can begin a new one.
// Retroactively end it right before the `$` char we parsed.
// (We can't use end_segment! here because it ends it right after
// the just-parsed character, which here would be '(' rather than '$')
// Don't push anything if the string would be empty.
if segment_parsed_bytes > 2 {
// exclude the 2 chars we just parsed, namely '$' and '('
let string_bytes = &state.bytes()[0..(segment_parsed_bytes - 2)];
match std::str::from_utf8(string_bytes) {
Ok(string) => {
state.advance_mut(string.len());
segments.push(StrSegment::Plaintext(string));
}
Err(_) => {
return Err((
MadeProgress,
EString::Space(BadInputError::BadUtf8, state.pos()),
));
}
}
}
// Advance past the `$(`
state.advance_mut(2);
let original_byte_count = state.bytes().len();
// Parse an arbitrary expression, followed by ')'
let (_progress, loc_expr, new_state) = skip_second!(
specialize_ref(
EString::Format,
loc(allocated(reset_min_indent(expr::expr_help())))
),
word1(b')', EString::FormatEnd)
)
.parse(arena, state, min_indent)?;
// Advance the iterator past the expr we just parsed.
for _ in 0..(original_byte_count - new_state.bytes().len()) {
bytes.next();
}
segments.push(StrSegment::Interpolated(loc_expr));
// Reset the segment
segment_parsed_bytes = 0;
state = new_state;
}
// If the '$' wasn't followed by '(', then this wasn't interpolation,
// and we don't need to do anything special.
}
b'\\' => {
// We're about to begin an escaped segment of some sort!
//
@ -510,10 +450,67 @@ pub fn parse_str_like_literal<'a>() -> impl Parser<'a, StrLikeLiteral<'a>, EStri
}
}
}
b'(' if preceded_by_dollar && !is_single_quote => {
// We're about to begin string interpolation!
//
// End the previous segment so we can begin a new one.
// Retroactively end it right before the `$` char we parsed.
// (We can't use end_segment! here because it ends it right after
// the just-parsed character, which here would be '(' rather than '$')
// Don't push anything if the string would be empty.
if segment_parsed_bytes > 2 {
// exclude the 2 chars we just parsed, namely '$' and '('
let string_bytes = &state.bytes()[0..(segment_parsed_bytes - 2)];
match std::str::from_utf8(string_bytes) {
Ok(string) => {
state.advance_mut(string.len());
segments.push(StrSegment::Plaintext(string));
}
Err(_) => {
return Err((
MadeProgress,
EString::Space(BadInputError::BadUtf8, state.pos()),
));
}
}
}
// Advance past the `$(`
state.advance_mut(2);
let original_byte_count = state.bytes().len();
// Parse an arbitrary expression, followed by ')'
let (_progress, loc_expr, new_state) = skip_second!(
specialize_ref(
EString::Format,
loc(allocated(reset_min_indent(expr::expr_help())))
),
word1(b')', EString::FormatEnd)
)
.parse(arena, state, min_indent)?;
// Advance the iterator past the expr we just parsed.
for _ in 0..(original_byte_count - new_state.bytes().len()) {
bytes.next();
}
segments.push(StrSegment::Interpolated(loc_expr));
// Reset the segment
segment_parsed_bytes = 0;
state = new_state;
}
_ => {
// All other characters need no special handling.
}
}
// iff the '$' is followed by '(', this is string interpolation.
// We'll check for the '(' on the next iteration of the loop.
preceded_by_dollar = byte == b'$';
}
// We ran out of characters before finding a closed quote

View file

@ -200,6 +200,36 @@ mod test_parse {
});
}
#[test]
fn string_of_just_dollar_sign() {
let arena = Bump::new();
assert_eq!(
Ok(Expr::Str(PlainLine("$"))),
parse_expr_with(&arena, arena.alloc(r#""$""#))
);
}
#[test]
fn string_beginning_with_dollar() {
let arena = Bump::new();
assert_eq!(
Ok(Expr::Str(PlainLine("$foo"))),
parse_expr_with(&arena, arena.alloc(r#""$foo""#))
);
}
#[test]
fn string_ending_with_dollar() {
let arena = Bump::new();
assert_eq!(
Ok(Expr::Str(PlainLine("foo$"))),
parse_expr_with(&arena, arena.alloc(r#""foo$""#))
);
}
#[test]
fn string_with_interpolation_in_back() {
assert_segments(r#""Hello $(name)""#, |arena| {

View file

@ -364,7 +364,7 @@ where
}
}
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct LineInfo {
line_offsets: Vec<u32>,
}

View file

@ -10,8 +10,7 @@ use roc_error_macros::internal_error;
use roc_module::symbol::{ModuleId, Symbol};
use roc_region::all::{Loc, Region};
use roc_solve_problem::{
NotDerivableContext, NotDerivableDecode, NotDerivableEncode, NotDerivableEq, TypeError,
UnderivableReason, Unfulfilled,
NotDerivableContext, NotDerivableEq, TypeError, UnderivableReason, Unfulfilled,
};
use roc_solve_schema::UnificationMode;
use roc_types::num::NumericRange;
@ -491,11 +490,6 @@ fn is_builtin_fixed_int_alias(symbol: Symbol) -> bool {
)
}
#[inline(always)]
fn is_builtin_nat_alias(symbol: Symbol) -> bool {
matches!(symbol, Symbol::NUM_NAT | Symbol::NUM_NATURAL)
}
#[inline(always)]
#[rustfmt::skip]
fn is_builtin_float_alias(symbol: Symbol) -> bool {
@ -513,7 +507,6 @@ fn is_builtin_dec_alias(symbol: Symbol) -> bool {
#[inline(always)]
fn is_builtin_number_alias(symbol: Symbol) -> bool {
is_builtin_fixed_int_alias(symbol)
|| is_builtin_nat_alias(symbol)
|| is_builtin_float_alias(symbol)
|| is_builtin_dec_alias(symbol)
}
@ -952,8 +945,7 @@ impl DerivableVisitor for DeriveEncoding {
#[inline(always)]
fn is_derivable_builtin_opaque(symbol: Symbol) -> bool {
(is_builtin_number_alias(symbol) && !is_builtin_nat_alias(symbol))
|| is_builtin_bool_alias(symbol)
is_builtin_number_alias(symbol) || is_builtin_bool_alias(symbol)
}
#[inline(always)]
@ -1020,19 +1012,8 @@ impl DerivableVisitor for DeriveEncoding {
}
#[inline(always)]
fn visit_alias(var: Variable, symbol: Symbol) -> Result<Descend, NotDerivable> {
if is_builtin_number_alias(symbol) {
if is_builtin_nat_alias(symbol) {
Err(NotDerivable {
var,
context: NotDerivableContext::Encode(NotDerivableEncode::Nat),
})
} else {
Ok(Descend(false))
}
} else {
Ok(Descend(true))
}
fn visit_alias(_var: Variable, symbol: Symbol) -> Result<Descend, NotDerivable> {
Ok(Descend(!is_builtin_number_alias(symbol)))
}
#[inline(always)]
@ -1057,8 +1038,7 @@ impl DerivableVisitor for DeriveDecoding {
#[inline(always)]
fn is_derivable_builtin_opaque(symbol: Symbol) -> bool {
(is_builtin_number_alias(symbol) && !is_builtin_nat_alias(symbol))
|| is_builtin_bool_alias(symbol)
is_builtin_number_alias(symbol) || is_builtin_bool_alias(symbol)
}
#[inline(always)]
@ -1091,9 +1071,9 @@ impl DerivableVisitor for DeriveDecoding {
if subs[field].is_optional() {
return Err(NotDerivable {
var,
context: NotDerivableContext::Decode(NotDerivableDecode::OptionalRecordField(
context: NotDerivableContext::DecodeOptionalRecordField(
subs[field_name].clone(),
)),
),
});
}
}
@ -1136,19 +1116,8 @@ impl DerivableVisitor for DeriveDecoding {
}
#[inline(always)]
fn visit_alias(var: Variable, symbol: Symbol) -> Result<Descend, NotDerivable> {
if is_builtin_number_alias(symbol) {
if is_builtin_nat_alias(symbol) {
Err(NotDerivable {
var,
context: NotDerivableContext::Decode(NotDerivableDecode::Nat),
})
} else {
Ok(Descend(false))
}
} else {
Ok(Descend(true))
}
fn visit_alias(_var: Variable, symbol: Symbol) -> Result<Descend, NotDerivable> {
Ok(Descend(!is_builtin_number_alias(symbol)))
}
#[inline(always)]
@ -1206,9 +1175,9 @@ impl DerivableVisitor for DeriveHash {
if subs[field].is_optional() {
return Err(NotDerivable {
var,
context: NotDerivableContext::Decode(NotDerivableDecode::OptionalRecordField(
context: NotDerivableContext::DecodeOptionalRecordField(
subs[field_name].clone(),
)),
),
});
}
}
@ -1282,7 +1251,6 @@ impl DerivableVisitor for DeriveEq {
#[inline(always)]
fn is_derivable_builtin_opaque(symbol: Symbol) -> bool {
is_builtin_fixed_int_alias(symbol)
|| is_builtin_nat_alias(symbol)
|| is_builtin_dec_alias(symbol)
|| is_builtin_bool_alias(symbol)
}
@ -1321,9 +1289,9 @@ impl DerivableVisitor for DeriveEq {
if subs[field].is_optional() {
return Err(NotDerivable {
var,
context: NotDerivableContext::Decode(NotDerivableDecode::OptionalRecordField(
context: NotDerivableContext::DecodeOptionalRecordField(
subs[field_name].clone(),
)),
),
});
}
}

View file

@ -165,7 +165,7 @@ mod solve_expr {
Str.fromUtf8
"
),
"List U8 -> Result Str [BadUtf8 Utf8ByteProblem Nat]",
"List U8 -> Result Str [BadUtf8 Utf8ByteProblem U64]",
);
}
@ -3016,7 +3016,7 @@ mod solve_expr {
infer_eq_without_problem(
indoc!(
r"
partition : Nat, Nat, List (Int a) -> [Pair Nat (List (Int a))]
partition : U64, U64, List (Int a) -> [Pair U64 (List (Int a))]
partition = \low, high, initialList ->
when List.get initialList high is
Ok _ ->
@ -3028,7 +3028,7 @@ mod solve_expr {
partition
"
),
"Nat, Nat, List (Int a) -> [Pair Nat (List (Int a))]",
"U64, U64, List (Int a) -> [Pair U64 (List (Int a))]",
);
}
@ -3037,7 +3037,7 @@ mod solve_expr {
infer_eq_without_problem(
indoc!(
r"
swap : Nat, Nat, List a -> List a
swap : U64, U64, List a -> List a
swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) ->
@ -3048,7 +3048,7 @@ mod solve_expr {
_ ->
list
partition : Nat, Nat, List (Int a) -> [Pair Nat (List (Int a))]
partition : U64, U64, List (Int a) -> [Pair U64 (List (Int a))]
partition = \low, high, initialList ->
when List.get initialList high is
Ok pivot ->
@ -3076,7 +3076,7 @@ mod solve_expr {
partition
"
),
"Nat, Nat, List (Int a) -> [Pair Nat (List (Int a))]",
"U64, U64, List (Int a) -> [Pair U64 (List (Int a))]",
);
}
@ -3116,7 +3116,7 @@ mod solve_expr {
List.get
"
),
"List a, Nat -> Result a [OutOfBounds]",
"List a, U64 -> Result a [OutOfBounds]",
);
}
@ -3772,7 +3772,7 @@ mod solve_expr {
fn list_walk_with_index_until() {
infer_eq_without_problem(
indoc!(r"List.walkWithIndexUntil"),
"List elem, state, (state, elem, Nat -> [Break state, Continue state]) -> state",
"List elem, state, (state, elem, U64 -> [Break state, Continue state]) -> state",
);
}
@ -3784,7 +3784,7 @@ mod solve_expr {
List.dropAt
"
),
"List elem, Nat -> List elem",
"List elem, U64 -> List elem",
);
}
@ -3820,7 +3820,7 @@ mod solve_expr {
List.takeFirst
"
),
"List elem, Nat -> List elem",
"List elem, U64 -> List elem",
);
}
@ -3832,7 +3832,7 @@ mod solve_expr {
List.takeLast
"
),
"List elem, Nat -> List elem",
"List elem, U64 -> List elem",
);
}
@ -3844,7 +3844,7 @@ mod solve_expr {
List.sublist
"
),
"List elem, { len : Nat, start : Nat } -> List elem",
"List elem, { len : U64, start : U64 } -> List elem",
);
}
@ -3852,7 +3852,7 @@ mod solve_expr {
fn list_split() {
infer_eq_without_problem(
indoc!("List.split"),
"List elem, Nat -> { before : List elem, others : List elem }",
"List elem, U64 -> { before : List elem, others : List elem }",
);
}
@ -3864,7 +3864,7 @@ mod solve_expr {
List.dropLast
"
),
"List elem, Nat -> List elem",
"List elem, U64 -> List elem",
);
}
@ -4400,7 +4400,7 @@ mod solve_expr {
r#"
app "test" provides [partitionHelp] to "./platform"
swap : Nat, Nat, List a -> List a
swap : U64, U64, List a -> List a
swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) ->
@ -4411,7 +4411,7 @@ mod solve_expr {
_ ->
[]
partitionHelp : Nat, Nat, List (Num a), Nat, (Num a) -> [Pair Nat (List (Num a))]
partitionHelp : U64, U64, List (Num a), U64, (Num a) -> [Pair U64 (List (Num a))]
partitionHelp = \i, j, list, high, pivot ->
if j < high then
when List.get list j is
@ -4427,7 +4427,7 @@ mod solve_expr {
Pair i list
"#
),
"Nat, Nat, List (Num a), Nat, Num a -> [Pair Nat (List (Num a))]",
"U64, U64, List (Num a), U64, Num a -> [Pair U64 (List (Num a))]",
);
}
@ -4981,8 +4981,6 @@ mod solve_expr {
i64: 123i64,
i128: 123i128,
nat: 123nat,
bu8: 0b11u8,
bu16: 0b11u16,
bu32: 0b11u32,
@ -4995,8 +4993,6 @@ mod solve_expr {
bi64: 0b11i64,
bi128: 0b11i128,
bnat: 0b11nat,
dec: 123.0dec,
f32: 123.0f32,
f64: 123.0f64,
@ -5007,7 +5003,7 @@ mod solve_expr {
}
"
),
r"{ bi128 : I128, bi16 : I16, bi32 : I32, bi64 : I64, bi8 : I8, bnat : Nat, bu128 : U128, bu16 : U16, bu32 : U32, bu64 : U64, bu8 : U8, dec : Dec, f32 : F32, f64 : F64, fdec : Dec, ff32 : F32, ff64 : F64, i128 : I128, i16 : I16, i32 : I32, i64 : I64, i8 : I8, nat : Nat, u128 : U128, u16 : U16, u32 : U32, u64 : U64, u8 : U8 }",
r"{ bi128 : I128, bi16 : I16, bi32 : I32, bi64 : I64, bi8 : I8, bu128 : U128, bu16 : U16, bu32 : U32, bu64 : U64, bu8 : U8, dec : Dec, f32 : F32, f64 : F64, fdec : Dec, ff32 : F32, ff64 : F64, i128 : I128, i16 : I16, i32 : I32, i64 : I64, i8 : I8, u128 : U128, u16 : U16, u32 : U32, u64 : U64, u8 : U8 }",
)
}
@ -5059,11 +5055,6 @@ mod solve_expr {
123i128 -> n
_ -> n),
nat: (\n ->
when n is
123nat -> n
_ -> n),
bu8: (\n ->
when n is
0b11u8 -> n
@ -5106,11 +5097,6 @@ mod solve_expr {
0b11i128 -> n
_ -> n),
bnat: (\n ->
when n is
0b11nat -> n
_ -> n),
dec: (\n ->
when n is
123.0dec -> n
@ -5139,7 +5125,7 @@ mod solve_expr {
}
"
),
r"{ bi128 : I128 -> I128, bi16 : I16 -> I16, bi32 : I32 -> I32, bi64 : I64 -> I64, bi8 : I8 -> I8, bnat : Nat -> Nat, bu128 : U128 -> U128, bu16 : U16 -> U16, bu32 : U32 -> U32, bu64 : U64 -> U64, bu8 : U8 -> U8, dec : Dec -> Dec, f32 : F32 -> F32, f64 : F64 -> F64, fdec : Dec -> Dec, ff32 : F32 -> F32, ff64 : F64 -> F64, i128 : I128 -> I128, i16 : I16 -> I16, i32 : I32 -> I32, i64 : I64 -> I64, i8 : I8 -> I8, nat : Nat -> Nat, u128 : U128 -> U128, u16 : U16 -> U16, u32 : U32 -> U32, u64 : U64 -> U64, u8 : U8 -> U8 }",
r"{ bi128 : I128 -> I128, bi16 : I16 -> I16, bi32 : I32 -> I32, bi64 : I64 -> I64, bi8 : I8 -> I8, bu128 : U128 -> U128, bu16 : U16 -> U16, bu32 : U32 -> U32, bu64 : U64 -> U64, bu8 : U8 -> U8, dec : Dec -> Dec, f32 : F32 -> F32, f64 : F64 -> F64, fdec : Dec -> Dec, ff32 : F32 -> F32, ff64 : F64 -> F64, i128 : I128 -> I128, i16 : I16 -> I16, i32 : I32 -> I32, i64 : I64 -> I64, i8 : I8 -> I8, u128 : U128 -> U128, u16 : U16 -> U16, u32 : U32 -> U32, u64 : U64 -> U64, u8 : U8 -> U8 }",
)
}
}

View file

@ -121,22 +121,10 @@ pub enum NotDerivableContext {
Function,
UnboundVar,
Opaque(Symbol),
Encode(NotDerivableEncode),
Decode(NotDerivableDecode),
DecodeOptionalRecordField(Lowercase),
Eq(NotDerivableEq),
}
#[derive(PartialEq, Eq, Debug, Clone)]
pub enum NotDerivableEncode {
Nat,
}
#[derive(PartialEq, Eq, Debug, Clone)]
pub enum NotDerivableDecode {
Nat,
OptionalRecordField(Lowercase),
}
#[derive(PartialEq, Eq, Debug, Clone)]
pub enum NotDerivableEq {
FloatingPoint,

View file

@ -1 +0,0 @@
/build

View file

@ -30,7 +30,7 @@ const ROC_LIST_MAP: &str = indoc::indoc!(
r#"
app "bench" provides [main] to "./platform"
main : List I64 -> Nat
main : List I64 -> U64
main = \list ->
list
|> List.map (\x -> x + 2)
@ -42,7 +42,7 @@ const ROC_LIST_MAP_WITH_INDEX: &str = indoc::indoc!(
r#"
app "bench" provides [main] to "./platform"
main : List I64 -> Nat
main : List I64 -> U64
main = \list ->
list
|> List.mapWithIndex (\x, _ -> x + 2)

View file

@ -40,7 +40,7 @@ const PURE_ROC_QUICKSORT: &str = indoc::indoc!(
quicksortHelp originalList 0 (n - 1)
quicksortHelp : List (Num a), Nat, Nat -> List (Num a)
quicksortHelp : List (Num a), U64, U64 -> List (Num a)
quicksortHelp = \list, low, high ->
if low < high then
when partition low high list is
@ -51,7 +51,7 @@ const PURE_ROC_QUICKSORT: &str = indoc::indoc!(
else
list
partition : Nat, Nat, List (Num a) -> [Pair Nat (List (Num a))]
partition : U64, U64, List (Num a) -> [Pair U64 (List (Num a))]
partition = \low, high, initialList ->
when List.get initialList high is
Ok pivot ->
@ -62,7 +62,7 @@ const PURE_ROC_QUICKSORT: &str = indoc::indoc!(
Err _ ->
Pair low initialList
partitionHelp : Nat, Nat, List (Num c), Nat, Num c -> [Pair Nat (List (Num c))]
partitionHelp : U64, U64, List (Num c), U64, Num c -> [Pair U64 (List (Num c))]
partitionHelp = \i, j, list, high, pivot ->
if j < high then
when List.get list j is

View file

@ -12,8 +12,6 @@ use roc_std::RocList;
#[cfg(all(test, any(feature = "gen-llvm", feature = "gen-wasm")))]
use roc_std::RocStr;
use crate::helpers::with_larger_debug_stack;
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn hash_specialization() {
@ -937,7 +935,7 @@ fn encode_derived_generic_tag_with_different_field_types() {
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn specialize_unique_newtype_records() {
with_larger_debug_stack(|| {
crate::helpers::with_larger_debug_stack(|| {
assert_evals_to!(
indoc!(
r#"
@ -1060,12 +1058,10 @@ mod decode_immediate {
#[cfg(all(test, feature = "gen-llvm"))]
use roc_std::RocStr;
use crate::helpers::with_larger_debug_stack;
#[test]
#[cfg(feature = "gen-llvm")]
fn string() {
with_larger_debug_stack(|| {
crate::helpers::with_larger_debug_stack(|| {
assert_evals_to!(
indoc!(
r#"
@ -1187,7 +1183,7 @@ mod decode_immediate {
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn decode_list_of_strings() {
with_larger_debug_stack(|| {
crate::helpers::with_larger_debug_stack(|| {
assert_evals_to!(
indoc!(
r#"
@ -1208,7 +1204,7 @@ fn decode_list_of_strings() {
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn encode_then_decode_list_of_strings() {
with_larger_debug_stack(|| {
crate::helpers::with_larger_debug_stack(|| {
assert_evals_to!(
indoc!(
r#"
@ -1230,7 +1226,7 @@ fn encode_then_decode_list_of_strings() {
#[cfg(feature = "gen-llvm")]
#[ignore = "#3696: Currently hits some weird panic in borrow checking, not sure if it's directly related to abilities."]
fn encode_then_decode_list_of_lists_of_strings() {
with_larger_debug_stack(|| {
crate::helpers::with_larger_debug_stack(|| {
assert_evals_to!(
indoc!(
r#"
@ -2149,7 +2145,7 @@ mod eq {
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn issue_4772_weakened_monomorphic_destructure() {
with_larger_debug_stack(|| {
crate::helpers::with_larger_debug_stack(|| {
assert_evals_to!(
indoc!(
r#"

View file

@ -25,7 +25,7 @@ fn dict_empty_len() {
"
),
0,
usize
u64
);
}
@ -41,7 +41,7 @@ fn dict_insert_empty() {
"
),
1,
usize
u64
);
}

View file

@ -1,6 +1,7 @@
#[cfg(feature = "gen-llvm")]
use crate::helpers::llvm::assert_evals_to_erased;
#[cfg(feature = "gen-llvm")]
use indoc::indoc;
#[test]
@ -32,13 +33,13 @@ fn multi_branch_capturing() {
f = \t, s ->
if t
then \{} -> 15nat
else \{} -> Str.countGraphemes s
then \{} -> 15u64
else \{} -> Str.countUtf8Bytes s
main = ((f Bool.true "abc") {}, (f Bool.false "abc") {})
"#
),
(15, 3),
(usize, usize)
(u64, u64)
);
}

View file

@ -821,6 +821,30 @@ fn list_append_longer_list() {
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn list_append_record() {
assert_evals_to!(
indoc!(
r#"
[
{ name: "foo", content: "cfoo" },
{ name: "bar", content: "cbar" },
{ name: "baz", content: "cbaz" },
]
|> List.append { name: "spam", content: "cspam" }
"#
),
RocList::from_slice(&[
(RocStr::from("cfoo"), RocStr::from("foo"),),
(RocStr::from("cbar"), RocStr::from("bar"),),
(RocStr::from("cbaz"), RocStr::from("baz"),),
(RocStr::from("cspam"), RocStr::from("spam"),),
]),
RocList<(RocStr, RocStr)>
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn list_prepend() {
@ -886,6 +910,25 @@ fn list_prepend_big_list() {
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn list_prepend_record() {
assert_evals_to!(
indoc!(
r"
payment1 : { amount: Dec, date: [RD I32] }
payment1 = { amount: 1dec, date: (RD 1000) }
payment2 : { amount: Dec, date: [RD I32] }
payment2 = { amount: 2dec, date: (RD 1001) }
List.prepend [payment2] payment1
"
),
RocList::from_slice(&[(RocDec::from(1), 1000i32), (RocDec::from(2), 1001i32),]),
RocList<(RocDec, i32)>
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn list_walk_backwards_empty_all_inline() {
@ -1014,7 +1057,7 @@ fn list_walk_implements_position() {
r"
Option a : [Some a, None]
find : List a, a -> Option Nat where a implements Eq
find : List a, a -> Option U64 where a implements Eq
find = \list, needle ->
findHelp list needle
|> .v
@ -1031,7 +1074,7 @@ fn list_walk_implements_position() {
Some v -> v
",
2,
usize
u64
);
}
@ -1178,7 +1221,7 @@ fn list_count_if_empty_list() {
"
),
0,
usize
u64
);
}
@ -1200,7 +1243,7 @@ fn list_count_if_always_true_for_non_empty_list() {
"
),
8,
usize
u64
);
}
@ -1218,7 +1261,7 @@ fn list_count_if_always_false_for_non_empty_list() {
"
),
0,
usize
u64
);
}
@ -1236,7 +1279,7 @@ fn list_count_if_condition() {
"
),
2,
usize
u64
);
}
@ -1250,7 +1293,7 @@ fn list_count_if_str() {
"#
),
2,
usize
u64
);
}
@ -1900,13 +1943,13 @@ fn list_concat_large() {
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn empty_list_len() {
assert_evals_to!("List.len []", 0, usize);
assert_evals_to!("List.len []", 0, u64);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn basic_int_list_len() {
assert_evals_to!("List.len [12, 9, 6, 3]", 4, usize);
assert_evals_to!("List.len [12, 9, 6, 3]", 4, u64);
}
#[test]
@ -1921,7 +1964,7 @@ fn loaded_int_list_len() {
"
),
3,
usize
u64
);
}
@ -1939,7 +1982,7 @@ fn fn_int_list_len() {
"
),
4,
usize
u64
);
}
@ -2359,7 +2402,7 @@ fn gen_wrap_len() {
"
),
RocList::from_slice(&[3]),
RocList<usize>
RocList<u64>
);
}
@ -2412,7 +2455,7 @@ fn gen_swap() {
app "quicksort" provides [main] to "./platform"
swap : Nat, Nat, List a -> List a
swap : U64, U64, List a -> List a
swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) ->
@ -2447,7 +2490,7 @@ fn gen_quicksort() {
quicksortHelp list 0 (n - 1)
quicksortHelp : List (Num a), Nat, Nat -> List (Num a)
quicksortHelp : List (Num a), U64, U64 -> List (Num a)
quicksortHelp = \list, low, high ->
if low < high then
when partition low high list is
@ -2459,7 +2502,7 @@ fn gen_quicksort() {
list
swap : Nat, Nat, List a -> List a
swap : U64, U64, List a -> List a
swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) ->
@ -2470,7 +2513,7 @@ fn gen_quicksort() {
_ ->
[]
partition : Nat, Nat, List (Num a) -> [Pair Nat (List (Num a))]
partition : U64, U64, List (Num a) -> [Pair U64 (List (Num a))]
partition = \low, high, initialList ->
when List.get initialList high is
Ok pivot ->
@ -2482,7 +2525,7 @@ fn gen_quicksort() {
Pair low initialList
partitionHelp : Nat, Nat, List (Num a), Nat, (Num a) -> [Pair Nat (List (Num a))]
partitionHelp : U64, U64, List (Num a), U64, (Num a) -> [Pair U64 (List (Num a))]
partitionHelp = \i, j, list, high, pivot ->
if j < high then
when List.get list j is
@ -2520,7 +2563,7 @@ fn quicksort() {
quicksortHelp list 0 (List.len list - 1)
quicksortHelp : List (Num a), Nat, Nat -> List (Num a)
quicksortHelp : List (Num a), U64, U64 -> List (Num a)
quicksortHelp = \list, low, high ->
if low < high then
when partition low high list is
@ -2532,7 +2575,7 @@ fn quicksort() {
list
swap : Nat, Nat, List a -> List a
swap : U64, U64, List a -> List a
swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) ->
@ -2543,7 +2586,7 @@ fn quicksort() {
_ ->
[]
partition : Nat, Nat, List (Num a) -> [Pair Nat (List (Num a))]
partition : U64, U64, List (Num a) -> [Pair U64 (List (Num a))]
partition = \low, high, initialList ->
when List.get initialList high is
Ok pivot ->
@ -2555,7 +2598,7 @@ fn quicksort() {
Pair low initialList
partitionHelp : Nat, Nat, List (Num a), Nat, Num a -> [Pair Nat (List (Num a))]
partitionHelp : U64, U64, List (Num a), U64, Num a -> [Pair U64 (List (Num a))]
partitionHelp = \i, j, list, high, pivot ->
# if j < high then
if Bool.false then
@ -2596,7 +2639,7 @@ fn quicksort_singleton() {
quicksortHelp list 0 (List.len list - 1)
quicksortHelp : List (Num a), Nat, Nat -> List (Num a)
quicksortHelp : List (Num a), U64, U64 -> List (Num a)
quicksortHelp = \list, low, high ->
if low < high then
when partition low high list is
@ -2608,7 +2651,7 @@ fn quicksort_singleton() {
list
swap : Nat, Nat, List a -> List a
swap : U64, U64, List a -> List a
swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) ->
@ -2619,7 +2662,7 @@ fn quicksort_singleton() {
_ ->
[]
partition : Nat, Nat, List (Num a) -> [Pair Nat (List (Num a))]
partition : U64, U64, List (Num a) -> [Pair U64 (List (Num a))]
partition = \low, high, initialList ->
when List.get initialList high is
Ok pivot ->
@ -2631,7 +2674,7 @@ fn quicksort_singleton() {
Pair low initialList
partitionHelp : Nat, Nat, List (Num a), Nat, Num a -> [Pair Nat (List (Num a))]
partitionHelp : U64, U64, List (Num a), U64, Num a -> [Pair U64 (List (Num a))]
partitionHelp = \i, j, list, high, pivot ->
if j < high then
when List.get list j is
@ -2672,7 +2715,7 @@ fn empty_list_increment_decrement() {
"
),
0,
usize
u64
);
}
@ -2689,7 +2732,7 @@ fn list_literal_increment_decrement() {
"
),
6,
usize
u64
);
}
@ -3158,7 +3201,7 @@ fn list_find() {
assert_evals_to!(
indoc!(
r#"
when List.findFirst ["a", "bc", "def", "g"] (\s -> Str.countGraphemes s > 1) is
when List.findFirst ["a", "bc", "def", "g"] (\s -> Str.countUtf8Bytes s > 1) is
Ok v -> v
Err _ -> "not found"
"#
@ -3170,7 +3213,7 @@ fn list_find() {
assert_evals_to!(
indoc!(
r#"
when List.findLast ["a", "bc", "def", "g"] (\s -> Str.countGraphemes s > 1) is
when List.findLast ["a", "bc", "def", "g"] (\s -> Str.countUtf8Bytes s > 1) is
Ok v -> v
Err _ -> "not found"
"#
@ -3186,7 +3229,7 @@ fn list_find_not_found() {
assert_evals_to!(
indoc!(
r#"
when List.findFirst ["a", "bc", "def", "g"] (\s -> Str.countGraphemes s > 5) is
when List.findFirst ["a", "bc", "def", "g"] (\s -> Str.countUtf8Bytes s > 5) is
Ok v -> v
Err _ -> "not found"
"#
@ -3198,7 +3241,7 @@ fn list_find_not_found() {
assert_evals_to!(
indoc!(
r#"
when List.findLast ["a", "bc", "def", "g"] (\s -> Str.countGraphemes s > 5) is
when List.findLast ["a", "bc", "def", "g"] (\s -> Str.countUtf8Bytes s > 5) is
Ok v -> v
Err _ -> "not found"
"#
@ -3214,7 +3257,7 @@ fn list_find_empty_typed_list() {
assert_evals_to!(
indoc!(
r#"
when List.findFirst [] (\s -> Str.countGraphemes s > 5) is
when List.findFirst [] (\s -> Str.countUtf8Bytes s > 5) is
Ok v -> v
Err _ -> "not found"
"#
@ -3226,7 +3269,7 @@ fn list_find_empty_typed_list() {
assert_evals_to!(
indoc!(
r#"
when List.findLast [] (\s -> Str.countGraphemes s > 5) is
when List.findLast [] (\s -> Str.countUtf8Bytes s > 5) is
Ok v -> v
Err _ -> "not found"
"#
@ -3270,25 +3313,25 @@ fn list_find_index() {
assert_evals_to!(
indoc!(
r#"
when List.findFirstIndex ["a", "bc", "def", "g"] (\s -> Str.countGraphemes s > 1) is
when List.findFirstIndex ["a", "bc", "def", "g"] (\s -> Str.countUtf8Bytes s > 1) is
Ok v -> v
Err _ -> 999
"#
),
1,
usize
u64
);
assert_evals_to!(
indoc!(
r#"
when List.findLastIndex ["a", "bc", "def", "g"] (\s -> Str.countGraphemes s > 1) is
when List.findLastIndex ["a", "bc", "def", "g"] (\s -> Str.countUtf8Bytes s > 1) is
Ok v -> v
Err _ -> 999
"#
),
2,
usize
u64
);
}
@ -3298,25 +3341,25 @@ fn list_find_index_not_found() {
assert_evals_to!(
indoc!(
r#"
when List.findFirstIndex ["a", "bc", "def", "g"] (\s -> Str.countGraphemes s > 5) is
when List.findFirstIndex ["a", "bc", "def", "g"] (\s -> Str.countUtf8Bytes s > 5) is
Ok v -> v
Err _ -> 999
"#
),
999,
usize
u64
);
assert_evals_to!(
indoc!(
r#"
when List.findLastIndex ["a", "bc", "def"] (\s -> Str.countGraphemes s > 5) is
when List.findLastIndex ["a", "bc", "def"] (\s -> Str.countUtf8Bytes s > 5) is
Ok v -> v
Err _ -> 999
"#
),
999,
usize
u64
);
}
@ -3326,25 +3369,25 @@ fn list_find_index_empty_typed_list() {
assert_evals_to!(
indoc!(
r"
when List.findFirstIndex [] (\s -> Str.countGraphemes s > 5) is
when List.findFirstIndex [] (\s -> Str.countUtf8Bytes s > 5) is
Ok v -> v
Err _ -> 999
"
),
999,
usize
u64
);
assert_evals_to!(
indoc!(
r"
when List.findLastIndex [] (\s -> Str.countGraphemes s > 5) is
when List.findLastIndex [] (\s -> Str.countUtf8Bytes s > 5) is
Ok v -> v
Err _ -> 999
"
),
999,
usize
u64
);
}
@ -3512,14 +3555,14 @@ fn monomorphized_lists() {
r"
l = \{} -> [1, 2, 3]
f : List U8, List U16 -> Nat
f : List U8, List U16 -> U64
f = \_, _ -> 18
f (l {}) (l {})
"
),
18,
usize
u64
)
}
@ -3742,7 +3785,7 @@ fn list_infer_usage() {
"#
),
1,
usize
u64
);
}
@ -3753,7 +3796,7 @@ fn list_walk_backwards_implements_position() {
r"
Option a : [Some a, None]
find : List a, a -> Option Nat where a implements Eq
find : List a, a -> Option U64 where a implements Eq
find = \list, needle ->
findHelp list needle
|> .v
@ -3770,7 +3813,7 @@ fn list_walk_backwards_implements_position() {
Some v -> v
",
0,
usize
u64
);
}

File diff suppressed because it is too large Load diff

View file

@ -963,7 +963,7 @@ fn overflow_frees_list() {
n : I64
n = 9_223_372_036_854_775_807 + (Num.intCast (List.len myList))
index : Nat
index : U64
index = Num.intCast n
List.get myList index
@ -1367,6 +1367,7 @@ fn linked_list_is_empty_2() {
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn linked_list_singleton() {
// verifies only that valid llvm is produced
assert_evals_to!(
indoc!(
r#"
@ -4590,7 +4591,7 @@ fn linked_list_trmc() {
LinkedList a : [Nil, Cons a (LinkedList a)]
repeat : a, Nat -> LinkedList a
repeat : a, U64 -> LinkedList a
repeat = \value, n ->
when n is
0 -> Nil

Some files were not shown because too many files have changed in this diff Show more