mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-03 19:58:18 +00:00
Merge branch 'main' into inline-imports
This commit is contained in:
commit
d5a38a26db
667 changed files with 22300 additions and 14562 deletions
|
@ -13,6 +13,3 @@ roc_module = { path = "../module" }
|
|||
roc_region = { path = "../region" }
|
||||
roc_target = { path = "../roc_target" }
|
||||
roc_error_macros = { path = "../../error_macros" }
|
||||
|
||||
tempfile.workspace = true
|
||||
|
||||
|
|
|
@ -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`.
|
||||
|
|
|
@ -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 };
|
||||
}
|
||||
|
@ -223,6 +226,10 @@ pub const RocDec = extern struct {
|
|||
return self.num;
|
||||
}
|
||||
|
||||
pub fn fromI128(num: i128) RocDec {
|
||||
return .{ .num = num };
|
||||
}
|
||||
|
||||
pub fn eq(self: RocDec, other: RocDec) bool {
|
||||
return self.num == other.num;
|
||||
}
|
||||
|
@ -340,6 +347,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 +1273,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) {
|
||||
|
@ -1254,6 +1479,10 @@ pub fn toI128(arg: RocDec) callconv(.C) i128 {
|
|||
return @call(.always_inline, RocDec.toI128, .{arg});
|
||||
}
|
||||
|
||||
pub fn fromI128(arg: i128) callconv(.C) RocDec {
|
||||
return @call(.always_inline, RocDec.fromI128, .{arg});
|
||||
}
|
||||
|
||||
pub fn eqC(arg1: RocDec, arg2: RocDec) callconv(.C) bool {
|
||||
return @call(.always_inline, RocDec.eq, .{ arg1, arg2 });
|
||||
}
|
||||
|
@ -1295,6 +1524,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 +1575,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 });
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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");
|
||||
|
@ -48,10 +49,15 @@ comptime {
|
|||
exportDecFn(dec.tanC, "tan");
|
||||
exportDecFn(dec.toF64, "to_f64");
|
||||
exportDecFn(dec.toI128, "to_i128");
|
||||
exportDecFn(dec.fromI128, "from_i128");
|
||||
exportDecFn(dec.toStr, "to_str");
|
||||
|
||||
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.");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -90,11 +96,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,6 +111,10 @@ comptime {
|
|||
exportNumFn(num.lessThanOrEqualU128, "less_than_or_equal.u128");
|
||||
exportNumFn(num.greaterThanU128, "greater_than.u128");
|
||||
exportNumFn(num.greaterThanOrEqualU128, "greater_than_or_equal.u128");
|
||||
exportNumFn(num.f32ToParts, "f32_to_parts");
|
||||
exportNumFn(num.f64ToParts, "f64_to_parts");
|
||||
exportNumFn(num.f32FromParts, "f32_from_parts");
|
||||
exportNumFn(num.f64FromParts, "f64_from_parts");
|
||||
|
||||
inline for (INTEGERS, 0..) |T, i| {
|
||||
num.exportPow(T, ROC_BUILTINS ++ "." ++ NUM ++ ".pow_int.");
|
||||
|
@ -122,6 +127,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.");
|
||||
|
@ -189,17 +197,17 @@ comptime {
|
|||
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.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.withCapacityC, "with_capacity");
|
||||
exportStrFn(str.strAllocationPtr, "allocation_ptr");
|
||||
exportStrFn(str.strReleaseExcessCapacity, "release_excess_capacity");
|
||||
|
||||
|
|
|
@ -15,6 +15,18 @@ pub fn NumParseResult(comptime T: type) type {
|
|||
};
|
||||
}
|
||||
|
||||
pub const F32Parts = extern struct {
|
||||
fraction: u32,
|
||||
exponent: u8,
|
||||
sign: bool,
|
||||
};
|
||||
|
||||
pub const F64Parts = extern struct {
|
||||
fraction: u64,
|
||||
exponent: u16,
|
||||
sign: bool,
|
||||
};
|
||||
|
||||
pub const U256 = struct {
|
||||
hi: u128,
|
||||
lo: u128,
|
||||
|
@ -86,6 +98,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 +295,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
|
||||
|
@ -657,3 +642,29 @@ pub fn exportCountOneBits(comptime T: type, comptime name: []const u8) void {
|
|||
}.func;
|
||||
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
|
||||
}
|
||||
|
||||
pub fn f32ToParts(self: f32) callconv(.C) F32Parts {
|
||||
const u32Value = @as(u32, @bitCast(self));
|
||||
return F32Parts{
|
||||
.fraction = u32Value & 0x7fffff,
|
||||
.exponent = @truncate(u32Value >> 23 & 0xff),
|
||||
.sign = u32Value >> 31 & 1 == 1,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn f64ToParts(self: f64) callconv(.C) F64Parts {
|
||||
const u64Value = @as(u64, @bitCast(self));
|
||||
return F64Parts{
|
||||
.fraction = u64Value & 0xfffffffffffff,
|
||||
.exponent = @truncate(u64Value >> 52 & 0x7ff),
|
||||
.sign = u64Value >> 63 & 1 == 1,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn f32FromParts(parts: F32Parts) callconv(.C) f32 {
|
||||
return @as(f32, @bitCast(parts.fraction & 0x7fffff | (@as(u32, parts.exponent) << 23) | (@as(u32, @intFromBool(parts.sign)) << 31)));
|
||||
}
|
||||
|
||||
pub fn f64FromParts(parts: F64Parts) callconv(.C) f64 {
|
||||
return @as(f64, @bitCast(parts.fraction & 0xfffffffffffff | (@as(u64, parts.exponent & 0x7ff) << 52) | (@as(u64, @intFromBool(parts.sign)) << 63)));
|
||||
}
|
||||
|
|
|
@ -625,64 +625,27 @@ fn initFromBigStr(slice_bytes: [*]u8, len: usize, alloc_ptr: usize) RocStr {
|
|||
}
|
||||
|
||||
fn strSplitHelp(array: [*]RocStr, string: RocStr, delimiter: RocStr) void {
|
||||
var ret_array_index: usize = 0;
|
||||
var slice_start_index: usize = 0;
|
||||
var str_index: usize = 0;
|
||||
|
||||
const bytes = string.asU8ptr();
|
||||
const len = string.len();
|
||||
const alloc_ptr = @intFromPtr(string.getAllocationPtr()) >> 1;
|
||||
const init_fn = if (string.isSmallStr())
|
||||
&initFromSmallStr
|
||||
else
|
||||
&initFromBigStr;
|
||||
|
||||
const delimiter_bytes_ptrs = delimiter.asU8ptr();
|
||||
const delimiter_len = delimiter.len();
|
||||
|
||||
if (len >= delimiter_len and delimiter_len > 0) {
|
||||
const end_index: usize = len - delimiter_len + 1;
|
||||
while (str_index <= end_index) {
|
||||
var delimiter_index: usize = 0;
|
||||
var matches_delimiter = true;
|
||||
|
||||
while (delimiter_index < delimiter_len) {
|
||||
var delimiterChar = delimiter_bytes_ptrs[delimiter_index];
|
||||
|
||||
if (str_index + delimiter_index >= len) {
|
||||
matches_delimiter = false;
|
||||
break;
|
||||
}
|
||||
|
||||
var strChar = bytes[str_index + delimiter_index];
|
||||
|
||||
if (delimiterChar != strChar) {
|
||||
matches_delimiter = false;
|
||||
break;
|
||||
}
|
||||
|
||||
delimiter_index += 1;
|
||||
}
|
||||
|
||||
if (matches_delimiter) {
|
||||
const segment_len: usize = str_index - slice_start_index;
|
||||
|
||||
array[ret_array_index] = init_fn(@constCast(bytes) + slice_start_index, segment_len, alloc_ptr);
|
||||
slice_start_index = str_index + delimiter_len;
|
||||
ret_array_index += 1;
|
||||
str_index += delimiter_len;
|
||||
} else {
|
||||
str_index += 1;
|
||||
}
|
||||
}
|
||||
if (delimiter.len() == 0) {
|
||||
string.incref(1);
|
||||
array[0] = string;
|
||||
return;
|
||||
}
|
||||
|
||||
array[ret_array_index] = init_fn(@constCast(bytes) + slice_start_index, len - slice_start_index, alloc_ptr);
|
||||
var it = std.mem.split(u8, string.asSlice(), delimiter.asSlice());
|
||||
|
||||
if (!string.isSmallStr()) {
|
||||
// Correct refcount for all of the splits made.
|
||||
string.incref(ret_array_index + 1);
|
||||
var i: usize = 0;
|
||||
var offset: usize = 0;
|
||||
|
||||
while (it.next()) |zig_slice| {
|
||||
const roc_slice = substringUnsafe(string, offset, zig_slice.len);
|
||||
array[i] = roc_slice;
|
||||
|
||||
i += 1;
|
||||
offset += zig_slice.len + delimiter.len();
|
||||
}
|
||||
|
||||
// Correct refcount for all of the splits made.
|
||||
string.incref(i); // i == array.len()
|
||||
}
|
||||
|
||||
test "strSplitHelp: empty delimiter" {
|
||||
|
@ -1008,44 +971,15 @@ test "strSplitHelp: overlapping delimiter 2" {
|
|||
// needs to be broken into, so that we can allocate a array
|
||||
// of that size. It always returns at least 1.
|
||||
pub fn countSegments(string: RocStr, delimiter: RocStr) callconv(.C) usize {
|
||||
const bytes = string.asU8ptr();
|
||||
const len = string.len();
|
||||
|
||||
const delimiter_bytes_ptrs = delimiter.asU8ptr();
|
||||
const delimiter_len = delimiter.len();
|
||||
|
||||
var count: usize = 1;
|
||||
|
||||
if (len >= delimiter_len and delimiter_len > 0) {
|
||||
var str_index: usize = 0;
|
||||
const end_cond: usize = len - delimiter_len + 1;
|
||||
|
||||
while (str_index < end_cond) {
|
||||
var delimiter_index: usize = 0;
|
||||
|
||||
var matches_delimiter = true;
|
||||
|
||||
while (delimiter_index < delimiter_len) {
|
||||
const delimiterChar = delimiter_bytes_ptrs[delimiter_index];
|
||||
const strChar = bytes[str_index + delimiter_index];
|
||||
|
||||
if (delimiterChar != strChar) {
|
||||
matches_delimiter = false;
|
||||
break;
|
||||
}
|
||||
|
||||
delimiter_index += 1;
|
||||
}
|
||||
|
||||
if (matches_delimiter) {
|
||||
count += 1;
|
||||
str_index += delimiter_len;
|
||||
} else {
|
||||
str_index += 1;
|
||||
}
|
||||
}
|
||||
if (delimiter.isEmpty()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
var it = std.mem.split(u8, string.asSlice(), delimiter.asSlice());
|
||||
var count: usize = 0;
|
||||
|
||||
while (it.next()) |_| : (count += 1) {}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
|
@ -1134,8 +1068,8 @@ test "countSegments: overlapping delimiter 2" {
|
|||
try expectEqual(segments_count, 3);
|
||||
}
|
||||
|
||||
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 {
|
||||
|
@ -1146,7 +1080,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;
|
||||
|
@ -1178,8 +1119,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" {
|
||||
|
@ -1242,7 +1183,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();
|
||||
|
||||
|
@ -1497,29 +1439,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(),
|
||||
|
@ -1527,11 +1465,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,
|
||||
|
@ -1539,25 +1477,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 };
|
||||
|
@ -1631,13 +1569,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;
|
||||
}
|
||||
|
||||
|
@ -1653,11 +1591,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 {
|
||||
|
@ -1674,7 +1612,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
|
||||
|
@ -1736,7 +1674,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));
|
||||
}
|
||||
|
||||
|
@ -2367,8 +2305,13 @@ test "capacity: big string" {
|
|||
try expect(data.getCapacity() >= data_bytes.len);
|
||||
}
|
||||
|
||||
pub fn reserve(string: RocStr, spare: usize) callconv(.C) RocStr {
|
||||
pub fn reserveC(string: RocStr, spare_u64: u64) callconv(.C) RocStr {
|
||||
return reserve(string, @intCast(spare_u64));
|
||||
}
|
||||
|
||||
fn reserve(string: RocStr, spare: usize) RocStr {
|
||||
const old_length = string.len();
|
||||
|
||||
if (string.getCapacity() >= old_length + spare) {
|
||||
return string;
|
||||
} else {
|
||||
|
@ -2378,11 +2321,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 strCloneTo(
|
||||
string: RocStr,
|
||||
ptr: [*]u8,
|
||||
|
|
|
@ -51,7 +51,7 @@ false = @Bool False
|
|||
## gate. The infix operator `&&` can also be used as shorthand for
|
||||
## `Bool.and`.
|
||||
##
|
||||
## ```
|
||||
## ```roc
|
||||
## expect (Bool.and Bool.true Bool.true) == Bool.true
|
||||
## expect (Bool.true && Bool.true) == Bool.true
|
||||
## expect (Bool.false && Bool.true) == Bool.false
|
||||
|
@ -66,7 +66,7 @@ false = @Bool False
|
|||
## In these languages the compiler will skip evaluating the expression after the
|
||||
## first operator under certain circumstances. For example an expression like
|
||||
## `enablePets && likesDogs user` would compile to.
|
||||
## ```
|
||||
## ```roc
|
||||
## if enablePets then
|
||||
## likesDogs user
|
||||
## else
|
||||
|
@ -80,7 +80,7 @@ and : Bool, Bool -> Bool
|
|||
## Returns `Bool.true` when either input is a `Bool.true`. This is equivalent to
|
||||
## the logic [OR](https://en.wikipedia.org/wiki/Logical_disjunction) gate.
|
||||
## The infix operator `||` can also be used as shorthand for `Bool.or`.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect (Bool.or Bool.false Bool.true) == Bool.true
|
||||
## expect (Bool.true || Bool.true) == Bool.true
|
||||
## expect (Bool.false || Bool.true) == Bool.true
|
||||
|
@ -98,7 +98,7 @@ or : Bool, Bool -> Bool
|
|||
## Returns `Bool.false` when given `Bool.true`, and vice versa. This is
|
||||
## equivalent to the logic [NOT](https://en.wikipedia.org/wiki/Negation)
|
||||
## gate. The operator `!` can also be used as shorthand for `Bool.not`.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect (Bool.not Bool.false) == Bool.true
|
||||
## expect (!Bool.false) == Bool.true
|
||||
## ```
|
||||
|
@ -111,7 +111,7 @@ not : Bool -> Bool
|
|||
##
|
||||
## **Note** that `isNotEq` does not accept arguments whose types contain
|
||||
## functions.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect (Bool.isNotEq Bool.false Bool.true) == Bool.true
|
||||
## expect (Bool.false != Bool.false) == Bool.false
|
||||
## expect "Apples" != "Oranges"
|
||||
|
|
|
@ -10,13 +10,13 @@ interface Box
|
|||
## the value from the stack to the heap. This may provide a performance
|
||||
## optimization for advanced use cases with large values. A platform may require
|
||||
## that some values are boxed.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Box.unbox (Box.box "Stack Faster") == "Stack Faster"
|
||||
## ```
|
||||
box : a -> Box a
|
||||
|
||||
## Returns a boxed value.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Box.unbox (Box.box "Stack Faster") == "Stack Faster"
|
||||
## ```
|
||||
unbox : Box a -> a
|
||||
|
|
|
@ -45,7 +45,6 @@ import Num exposing [
|
|||
I32,
|
||||
I64,
|
||||
I128,
|
||||
Nat,
|
||||
F32,
|
||||
F64,
|
||||
Dec,
|
||||
|
@ -60,7 +59,7 @@ DecodeError : [TooShort]
|
|||
## This can be useful when creating a [custom](#custom) decoder or when
|
||||
## using [fromBytesPartial](#fromBytesPartial). For example writing unit tests,
|
||||
## such as;
|
||||
## ```
|
||||
## ```roc
|
||||
## expect
|
||||
## input = "\"hello\", " |> Str.toUtf8
|
||||
## actual = Decode.fromBytesPartial input Json.json
|
||||
|
@ -104,7 +103,7 @@ DecoderFormatting implements
|
|||
## `Skip` if the field is not a part of the decoded record.
|
||||
##
|
||||
## `finalizer` should produce the record value from the decoded `state`.
|
||||
record : state, (state, Str -> [Keep (Decoder state fmt), Skip]), (state -> Result val DecodeError) -> Decoder val fmt where fmt implements DecoderFormatting
|
||||
record : state, (state, Str -> [Keep (Decoder state fmt), Skip]), (state, fmt -> Result val DecodeError) -> Decoder val fmt where fmt implements DecoderFormatting
|
||||
|
||||
## `tuple state stepElem finalizer` decodes a tuple element-by-element.
|
||||
##
|
||||
|
@ -113,12 +112,12 @@ 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;
|
||||
##
|
||||
## ```
|
||||
## ```roc
|
||||
## decodeBool = Decode.custom \bytes, @Json {} ->
|
||||
## when bytes is
|
||||
## ['f', 'a', 'l', 's', 'e', ..] -> { result: Ok Bool.false, rest: List.dropFirst bytes 5 }
|
||||
|
@ -133,7 +132,7 @@ decodeWith : List U8, Decoder val fmt, fmt -> DecodeResult val where fmt impleme
|
|||
decodeWith = \bytes, @Decoder decode, fmt -> decode bytes fmt
|
||||
|
||||
## Decode a `List U8` utf-8 bytes and return a [DecodeResult](#DecodeResult)
|
||||
## ```
|
||||
## ```roc
|
||||
## expect
|
||||
## input = "\"hello\", " |> Str.toUtf8
|
||||
## actual = Decode.fromBytesPartial input Json.json
|
||||
|
@ -147,7 +146,7 @@ fromBytesPartial = \bytes, fmt -> decodeWith bytes decoder fmt
|
|||
## Decode a `List U8` utf-8 bytes and return a [Result] with no leftover bytes
|
||||
## expected. If successful returns `Ok val`, however, if there are bytes
|
||||
## remaining returns `Err Leftover (List U8)`.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect
|
||||
## input = "\"hello\", " |> Str.toUtf8
|
||||
## actual = Decode.fromBytes input Json.json
|
||||
|
|
|
@ -35,7 +35,7 @@ import Bool exposing [Bool, Eq]
|
|||
import Result exposing [Result]
|
||||
import List
|
||||
import Str
|
||||
import Num exposing [Nat, U64, F32, U32, U8]
|
||||
import Num exposing [U64, F32, U32, U8]
|
||||
import Hash exposing [Hasher, Hash]
|
||||
import Inspect exposing [Inspect, Inspector, InspectFormatter]
|
||||
|
||||
|
@ -53,7 +53,7 @@ import Inspect exposing [Inspect, Inspector, InspectFormatter]
|
|||
##
|
||||
## Here's an example of a dictionary which uses a city's name as the key, and
|
||||
## its population as the associated value.
|
||||
## ```
|
||||
## ```roc
|
||||
## populationByCity =
|
||||
## Dict.empty {}
|
||||
## |> Dict.insert "London" 8_961_989
|
||||
|
@ -74,7 +74,7 @@ import Inspect exposing [Inspect, Inspector, InspectFormatter]
|
|||
## ## Removing
|
||||
##
|
||||
## We can remove an element from the dictionary, like so:
|
||||
## ```
|
||||
## ```roc
|
||||
## populationByCity
|
||||
## |> Dict.remove "Philadelphia"
|
||||
## |> Dict.keys
|
||||
|
@ -135,7 +135,7 @@ toInspectorDict = \dict ->
|
|||
Inspect.apply (Inspect.dict dict walk Inspect.toInspector Inspect.toInspector) fmt
|
||||
|
||||
## Return an empty dictionary.
|
||||
## ```
|
||||
## ```roc
|
||||
## emptyDict = Dict.empty {}
|
||||
## ```
|
||||
empty : {} -> Dict * *
|
||||
|
@ -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
|
||||
|
@ -201,19 +200,19 @@ releaseExcessCapacity = \@Dict { buckets, data, maxBucketCapacity: originalMaxBu
|
|||
@Dict { buckets, data, maxBucketCapacity: originalMaxBucketCapacity, maxLoadFactor, shifts }
|
||||
|
||||
## Returns the max number of elements the dictionary can hold before requiring a rehash.
|
||||
## ```
|
||||
## ```roc
|
||||
## foodDict =
|
||||
## Dict.empty {}
|
||||
## |> Dict.insert "apple" "fruit"
|
||||
##
|
||||
## 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.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect
|
||||
## Dict.single "A" "B"
|
||||
## |> Bool.isEq (Dict.insert (Dict.empty {}) "A" "B")
|
||||
|
@ -223,7 +222,7 @@ single = \k, v ->
|
|||
insert (empty {}) k v
|
||||
|
||||
## Returns dictionary with the keys and values specified by the input [List].
|
||||
## ```
|
||||
## ```roc
|
||||
## expect
|
||||
## Dict.single 1 "One"
|
||||
## |> Dict.insert 2 "Two"
|
||||
|
@ -242,7 +241,7 @@ fromList = \data ->
|
|||
List.walk data (empty {}) (\dict, (k, v) -> insert dict k v)
|
||||
|
||||
## Returns the number of values in the dictionary.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect
|
||||
## Dict.empty {}
|
||||
## |> Dict.insert "One" "A Song"
|
||||
|
@ -251,12 +250,12 @@ fromList = \data ->
|
|||
## |> Dict.len
|
||||
## |> Bool.isEq 3
|
||||
## ```
|
||||
len : Dict * * -> Nat
|
||||
len : Dict * * -> U64
|
||||
len = \@Dict { data } ->
|
||||
List.len data
|
||||
|
||||
## Check if the dictionary is empty.
|
||||
## ```
|
||||
## ```roc
|
||||
## Dict.isEmpty (Dict.empty {} |> Dict.insert "key" 42)
|
||||
##
|
||||
## Dict.isEmpty (Dict.empty {})
|
||||
|
@ -266,7 +265,7 @@ isEmpty = \@Dict { data } ->
|
|||
List.isEmpty data
|
||||
|
||||
## Clears all elements from a dictionary keeping around the allocation if it isn't huge.
|
||||
## ```
|
||||
## ```roc
|
||||
## songs =
|
||||
## Dict.empty {}
|
||||
## |> Dict.insert "One" "A Song"
|
||||
|
@ -313,7 +312,7 @@ joinMap = \dict, transform ->
|
|||
## Iterate through the keys and values in the dictionary and call the provided
|
||||
## function with signature `state, k, v -> state` for each value, with an
|
||||
## initial `state` value provided for the first call.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect
|
||||
## Dict.empty {}
|
||||
## |> Dict.insert "Apples" 12
|
||||
|
@ -336,7 +335,7 @@ walk = \@Dict { data }, initialState, transform ->
|
|||
##
|
||||
## As such, it is typically better for performance to use this over [Dict.walk]
|
||||
## if returning `Break` earlier than the last element is expected to be common.
|
||||
## ```
|
||||
## ```roc
|
||||
## people =
|
||||
## Dict.empty {}
|
||||
## |> Dict.insert "Alice" 17
|
||||
|
@ -359,7 +358,7 @@ walkUntil = \@Dict { data }, initialState, transform ->
|
|||
|
||||
## Run the given function on each key-value pair of a dictionary, and return
|
||||
## a dictionary with just the pairs for which the function returned `Bool.true`.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Dict.empty {}
|
||||
## |> Dict.insert "Alice" 17
|
||||
## |> Dict.insert "Bob" 18
|
||||
|
@ -372,20 +371,20 @@ 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
|
||||
|
||||
## Run the given function on each key-value pair of a dictionary, and return
|
||||
## a dictionary with just the pairs for which the function returned `Bool.false`.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Dict.empty {}
|
||||
## |> Dict.insert "Alice" 17
|
||||
## |> Dict.insert "Bob" 18
|
||||
|
@ -400,7 +399,7 @@ dropIf = \dict, predicate ->
|
|||
|
||||
## Get the value for a given key. If there is a value for the specified key it
|
||||
## will return [Ok value], otherwise return [Err KeyNotFound].
|
||||
## ```
|
||||
## ```roc
|
||||
## dictionary =
|
||||
## Dict.empty {}
|
||||
## |> Dict.insert 1 "Apple"
|
||||
|
@ -415,7 +414,7 @@ get = \dict, key ->
|
|||
|> .result
|
||||
|
||||
## Check if the dictionary has a value for a specified key.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect
|
||||
## Dict.empty {}
|
||||
## |> Dict.insert 1234 "5678"
|
||||
|
@ -429,7 +428,7 @@ contains = \dict, key ->
|
|||
|> Result.isOk
|
||||
|
||||
## Insert a value into the dictionary at a specified key.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect
|
||||
## Dict.empty {}
|
||||
## |> Dict.insert "Apples" 12
|
||||
|
@ -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
|
||||
|
@ -473,7 +472,7 @@ insertHelper = \buckets0, data0, bucketIndex0, distAndFingerprint0, key, value,
|
|||
insertHelper buckets0 data0 bucketIndex1 distAndFingerprint1 key value maxBucketCapacity maxLoadFactor shifts
|
||||
|
||||
## Remove a value from the dictionary for a specified key.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect
|
||||
## Dict.empty {}
|
||||
## |> Dict.insert "Some" "Value"
|
||||
|
@ -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
|
||||
|
@ -510,7 +510,7 @@ removeHelper = \buckets, bucketIndex, distAndFingerprint, data, key ->
|
|||
## performance optimization for the use case of providing a default when a value
|
||||
## is missing. This is more efficient than doing both a `Dict.get` and then a
|
||||
## `Dict.insert` call, and supports being piped.
|
||||
## ```
|
||||
## ```roc
|
||||
## alterValue : [Present Bool, Missing] -> [Present Bool, Missing]
|
||||
## alterValue = \possibleValue ->
|
||||
## when possibleValue is
|
||||
|
@ -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
|
||||
|
@ -573,7 +573,7 @@ circularDist = \start, end, size ->
|
|||
|
||||
## Returns the keys and values of a dictionary as a [List].
|
||||
## This requires allocating a temporary list, prefer using [Dict.toList] or [Dict.walk] instead.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect
|
||||
## Dict.single 1 "One"
|
||||
## |> Dict.insert 2 "Two"
|
||||
|
@ -588,7 +588,7 @@ toList = \@Dict { data } ->
|
|||
|
||||
## Returns the keys of a dictionary as a [List].
|
||||
## This requires allocating a temporary [List], prefer using [Dict.toList] or [Dict.walk] instead.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect
|
||||
## Dict.single 1 "One"
|
||||
## |> Dict.insert 2 "Two"
|
||||
|
@ -603,7 +603,7 @@ keys = \@Dict { data } ->
|
|||
|
||||
## Returns the values of a dictionary as a [List].
|
||||
## This requires allocating a temporary [List], prefer using [Dict.toList] or [Dict.walk] instead.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect
|
||||
## Dict.single 1 "One"
|
||||
## |> Dict.insert 2 "Two"
|
||||
|
@ -621,7 +621,7 @@ values = \@Dict { data } ->
|
|||
## both dictionaries will be combined. Note that where there are pairs
|
||||
## with the same key, the value contained in the second input will be
|
||||
## retained, and the value in the first input will be removed.
|
||||
## ```
|
||||
## ```roc
|
||||
## first =
|
||||
## Dict.single 1 "Not Me"
|
||||
## |> Dict.insert 2 "And Me"
|
||||
|
@ -650,7 +650,7 @@ insertAll = \xs, ys ->
|
|||
## Combine two dictionaries by keeping the [intersection](https://en.wikipedia.org/wiki/Intersection_(set_theory))
|
||||
## of all the key-value pairs. This means that we keep only those pairs
|
||||
## that are in both dictionaries. Both the key and value must match to be kept.
|
||||
## ```
|
||||
## ```roc
|
||||
## first =
|
||||
## Dict.single 1 "Keep Me"
|
||||
## |> Dict.insert 2 "And Me"
|
||||
|
@ -691,7 +691,7 @@ keepShared = \xs0, ys0 ->
|
|||
## using the [set difference](https://en.wikipedia.org/wiki/Complement_(set_theory)#Relative_complement)
|
||||
## of the values. This means that we will be left with only those pairs that
|
||||
## are in the first dictionary and whose keys are not in the second.
|
||||
## ```
|
||||
## ```roc
|
||||
## first =
|
||||
## Dict.single 1 "Keep Me"
|
||||
## |> Dict.insert 2 "And Me"
|
||||
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
##
|
||||
## ```roc
|
||||
## 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.
|
||||
##
|
||||
## ```roc
|
||||
## 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.
|
||||
##
|
||||
## ```roc
|
||||
## 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
|
||||
|
|
|
@ -15,7 +15,6 @@ interface Hash
|
|||
hashI32,
|
||||
hashI64,
|
||||
hashI128,
|
||||
hashNat,
|
||||
hashDec,
|
||||
complete,
|
||||
hashStrBytes,
|
||||
|
@ -37,7 +36,6 @@ import Num exposing [
|
|||
I32,
|
||||
I64,
|
||||
I128,
|
||||
Nat,
|
||||
Dec,
|
||||
]
|
||||
|
||||
|
@ -111,27 +109,9 @@ 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
|
||||
|
||||
## Adds a single [Dec] to a hasher.
|
||||
hashDec : a, Dec -> a where a implements Hasher
|
||||
hashDec = \hasher, n -> hashI128 hasher (i128OfDec n)
|
||||
hashDec = \hasher, n -> hashI128 hasher (Num.withoutDecimalPoint n)
|
||||
|
||||
## Adds a container of [Hash]able elements to a [Hasher] by hashing each element.
|
||||
## The container is iterated using the walk method passed in.
|
||||
|
|
|
@ -27,7 +27,6 @@ interface Inspect
|
|||
i64,
|
||||
u128,
|
||||
i128,
|
||||
nat,
|
||||
f32,
|
||||
f64,
|
||||
dec,
|
||||
|
@ -39,7 +38,7 @@ interface Inspect
|
|||
imports []
|
||||
|
||||
import Bool exposing [Bool]
|
||||
import Num exposing [U8, U16, U32, U64, U128, I8, I16, I32, I64, I128, F32, F64, Dec, Nat]
|
||||
import Num exposing [U8, U16, U32, U64, U128, I8, I16, I32, I64, I128, F32, F64, Dec]
|
||||
import List
|
||||
import 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
|
||||
|
|
|
@ -75,12 +75,12 @@ interface List
|
|||
|
||||
import Bool exposing [Bool, Eq]
|
||||
import Result exposing [Result]
|
||||
import Num exposing [Nat, Num]
|
||||
import Num exposing [U64, Num]
|
||||
|
||||
## ## Types
|
||||
##
|
||||
## A sequential list of values.
|
||||
## ```
|
||||
## ```roc
|
||||
## [1, 2, 3] # a list of numbers
|
||||
## ["a", "b", "c"] # a list of strings
|
||||
## [[1.1], [], [2.2, 3.3]] # a list of lists of numbers
|
||||
|
@ -91,14 +91,14 @@ import Num exposing [Nat, Num]
|
|||
## 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
|
||||
|
@ -112,7 +112,7 @@ import Num exposing [Nat, Num]
|
|||
## will be immediately freed.
|
||||
##
|
||||
## Let's look at an example.
|
||||
## ```
|
||||
## ```roc
|
||||
## ratings = [5, 4, 3]
|
||||
##
|
||||
## { foo: ratings, bar: ratings }
|
||||
|
@ -125,7 +125,7 @@ import Num exposing [Nat, Num]
|
|||
## refcount getting incremented from 1 to 3.
|
||||
##
|
||||
## Let's turn this example into a function.
|
||||
## ```
|
||||
## ```roc
|
||||
## getRatings = \first ->
|
||||
## ratings = [first, 4, 3]
|
||||
##
|
||||
|
@ -147,7 +147,7 @@ import Num exposing [Nat, Num]
|
|||
## list, and that list has a refcount of 2.
|
||||
##
|
||||
## Let's change the last line to be `(getRatings 5).bar` instead of `getRatings 5`:
|
||||
## ```
|
||||
## ```roc
|
||||
## getRatings = \first ->
|
||||
## ratings = [first, 4, 3]
|
||||
##
|
||||
|
@ -161,7 +161,7 @@ import Num exposing [Nat, Num]
|
|||
## where it started: there is only 1 reference to it.
|
||||
##
|
||||
## Finally let's suppose the final line were changed to this:
|
||||
## ```
|
||||
## ```roc
|
||||
## List.first (getRatings 5).bar
|
||||
## ```
|
||||
## This call to [List.first] means that even the list in the `bar` field has become
|
||||
|
@ -174,7 +174,7 @@ import Num exposing [Nat, Num]
|
|||
## and then with a list of lists, to see how they differ.
|
||||
##
|
||||
## Here's the example using a list of numbers.
|
||||
## ```
|
||||
## ```roc
|
||||
## nums = [1, 2, 3, 4, 5, 6, 7]
|
||||
##
|
||||
## first = List.first nums
|
||||
|
@ -185,7 +185,7 @@ import Num exposing [Nat, Num]
|
|||
## It makes a list, calls [List.first] and [List.last] on it, and then returns `first`.
|
||||
##
|
||||
## Here's the equivalent code with a list of lists:
|
||||
## ```
|
||||
## ```roc
|
||||
## lists = [[1], [2, 3], [], [4, 5, 6, 7]]
|
||||
##
|
||||
## first = List.first lists
|
||||
|
@ -216,7 +216,7 @@ import Num exposing [Nat, Num]
|
|||
# separator so List.isEmpty doesn't absorb the above into its doc comment
|
||||
|
||||
## Check if the list is empty.
|
||||
## ```
|
||||
## ```roc
|
||||
## List.isEmpty [1, 2, 3]
|
||||
##
|
||||
## List.isEmpty []
|
||||
|
@ -227,16 +227,16 @@ 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.
|
||||
##
|
||||
## Returns `Err OutOfBounds` if the given index exceeds the List's length
|
||||
## ```
|
||||
## ```roc
|
||||
## 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
|
||||
|
@ -255,19 +255,19 @@ replace = \list, index, newValue ->
|
|||
{ list, value: newValue }
|
||||
|
||||
## Replaces the element at the given index with a replacement.
|
||||
## ```
|
||||
## ```roc
|
||||
## List.set ["a", "b", "c"] 1 "B"
|
||||
## ```
|
||||
## If the given index is outside the bounds of the list, returns the original
|
||||
## 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
|
||||
|
||||
## Updates the element at the given index with the given function.
|
||||
## ```
|
||||
## ```roc
|
||||
## List.update [1, 2, 3] 1 (\x -> x + 1)
|
||||
## ```
|
||||
## If the given index is outside the bounds of the list, returns the original
|
||||
|
@ -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
|
||||
|
@ -311,7 +311,7 @@ expect
|
|||
got == want
|
||||
|
||||
## Add a single element to the end of a list.
|
||||
## ```
|
||||
## ```roc
|
||||
## List.append [1, 2, 3] 4
|
||||
##
|
||||
## [0, 1, 2]
|
||||
|
@ -326,7 +326,7 @@ append = \list, element ->
|
|||
## If the given [Result] is `Ok`, add it to the end of a list.
|
||||
## Otherwise, return the list unmodified.
|
||||
##
|
||||
## ```
|
||||
## ```roc
|
||||
## List.appendIfOk [1, 2, 3] (Ok 4)
|
||||
##
|
||||
## [0, 1, 2]
|
||||
|
@ -346,7 +346,7 @@ appendIfOk = \list, result ->
|
|||
appendUnsafe : List a, a -> List a
|
||||
|
||||
## Add a single element to the beginning of a list.
|
||||
## ```
|
||||
## ```roc
|
||||
## List.prepend [1, 2, 3] 0
|
||||
##
|
||||
## [2, 3, 4]
|
||||
|
@ -357,7 +357,7 @@ prepend : List a, a -> List a
|
|||
## If the given [Result] is `Ok`, add it to the beginning of a list.
|
||||
## Otherwise, return the list unmodified.
|
||||
##
|
||||
## ```
|
||||
## ```roc
|
||||
## List.prepend [1, 2, 3] (Ok 0)
|
||||
##
|
||||
## [2, 3, 4]
|
||||
|
@ -371,23 +371,22 @@ prependIfOk = \list, result ->
|
|||
|
||||
## Returns the length of the list - the number of elements it contains.
|
||||
##
|
||||
## 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
|
||||
## One [List] can store up to `Num.maxI64` elements on 64-bit targets and `Num.maxI32` on 32-bit targets like wasm.
|
||||
## This means the #U64 this function returns can always be safely converted to #I64 or #I32, depending on the target.
|
||||
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.
|
||||
releaseExcessCapacity : List a -> List a
|
||||
|
||||
## Put two lists together.
|
||||
## ```
|
||||
## ```roc
|
||||
## List.concat [1, 2, 3] [4, 5]
|
||||
##
|
||||
## [0, 1, 2]
|
||||
|
@ -396,7 +395,7 @@ releaseExcessCapacity : List a -> List a
|
|||
concat : List a, List a -> List a
|
||||
|
||||
## Returns the last element in the list, or `ListWasEmpty` if it was empty.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect List.last [1, 2, 3] == Ok 3
|
||||
## expect List.last [] == Err ListWasEmpty
|
||||
## ```
|
||||
|
@ -409,7 +408,7 @@ last = \list ->
|
|||
## A list with a single element in it.
|
||||
##
|
||||
## This is useful in pipelines, like so:
|
||||
## ```
|
||||
## ```roc
|
||||
## websites =
|
||||
## Str.concat domain ".com"
|
||||
## |> List.single
|
||||
|
@ -418,11 +417,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)
|
||||
|
@ -430,7 +429,7 @@ repeatHelp = \value, count, accum ->
|
|||
accum
|
||||
|
||||
## Returns the list with its elements reversed.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect List.reverse [1, 2, 3] == [3, 2, 1]
|
||||
## ```
|
||||
reverse : List a -> List a
|
||||
|
@ -448,7 +447,7 @@ reverseHelp = \list, left, right ->
|
|||
clone : List a -> List a
|
||||
|
||||
## Join the given lists together into one list.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect List.join [[1], [2, 3], [], [4, 5]] == [1, 2, 3, 4, 5]
|
||||
## expect List.join [[], []] == []
|
||||
## expect List.join [] == []
|
||||
|
@ -471,7 +470,7 @@ contains = \list, needle ->
|
|||
## which updates the `state`. It returns the final `state` at the end.
|
||||
##
|
||||
## You can use it in a pipeline:
|
||||
## ```
|
||||
## ```roc
|
||||
## [2, 4, 8]
|
||||
## |> List.walk 0 Num.add
|
||||
## ```
|
||||
|
@ -490,7 +489,7 @@ contains = \list, needle ->
|
|||
## 6 | 8 | 14
|
||||
##
|
||||
## The following returns -6:
|
||||
## ```
|
||||
## ```roc
|
||||
## [1, 2, 3]
|
||||
## |> List.walk 0 Num.sub
|
||||
## ```
|
||||
|
@ -501,7 +500,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)
|
||||
|
@ -511,12 +510,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
|
||||
|
@ -526,14 +525,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
|
||||
|
@ -551,7 +550,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
|
||||
|
@ -586,7 +585,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)
|
||||
|
@ -595,7 +594,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
|
||||
|
@ -639,7 +638,7 @@ all = \list, predicate ->
|
|||
|
||||
## Run the given function on each element of a list, and return all the
|
||||
## elements for which the function returned `Bool.true`.
|
||||
## ```
|
||||
## ```roc
|
||||
## List.keepIf [1, 2, 3, 4] (\num -> num > 2)
|
||||
## ```
|
||||
## ## Performance Details
|
||||
|
@ -664,7 +663,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
|
||||
|
@ -676,7 +675,7 @@ keepIfHelp = \list, predicate, kept, index, length ->
|
|||
|
||||
## Run the given function on each element of a list, and return all the
|
||||
## elements for which the function returned `Bool.false`.
|
||||
## ```
|
||||
## ```roc
|
||||
## List.dropIf [1, 2, 3, 4] (\num -> num > 2)
|
||||
## ```
|
||||
## ## Performance Details
|
||||
|
@ -689,11 +688,11 @@ dropIf = \list, predicate ->
|
|||
|
||||
## Run the given function on each element of a list, and return the
|
||||
## number of elements for which the function returned `Bool.true`.
|
||||
## ```
|
||||
## ```roc
|
||||
## 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
|
||||
|
@ -705,7 +704,7 @@ countIf = \list, predicate ->
|
|||
|
||||
## This works like [List.map], except only the transformed values that are
|
||||
## wrapped in `Ok` are kept. Any that are wrapped in `Err` are dropped.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect List.keepOks ["1", "Two", "23", "Bird"] Str.toI32 == [1, 23]
|
||||
##
|
||||
## expect List.keepOks [["a", "b"], [], ["c", "d", "e"], [] ] List.first == ["a", "c"]
|
||||
|
@ -724,7 +723,7 @@ keepOks = \list, toResult ->
|
|||
|
||||
## This works like [List.map], except only the transformed values that are
|
||||
## wrapped in `Err` are kept. Any that are wrapped in `Ok` are dropped.
|
||||
## ```
|
||||
## ```roc
|
||||
## List.keepErrs [["a", "b"], [], [], ["c", "d", "e"]] List.last
|
||||
##
|
||||
## fn = \str -> if Str.isEmpty str then Err StrWasEmpty else Ok (Str.len str)
|
||||
|
@ -742,7 +741,7 @@ keepErrs = \list, toResult ->
|
|||
|
||||
## Convert each element in the list to something new, by calling a conversion
|
||||
## function on each of them. Then return a new list of the converted values.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect List.map [1, 2, 3] (\num -> num + 1) == [2, 3, 4]
|
||||
##
|
||||
## expect List.map ["", "a", "bc"] Str.isEmpty == [Bool.true, Bool.false, Bool.false]
|
||||
|
@ -755,7 +754,7 @@ map : List a, (a -> b) -> List b
|
|||
##
|
||||
## Some languages have a function named `zip`, which does something similar to
|
||||
## calling [List.map2] passing two lists and `Pair`:
|
||||
## ```
|
||||
## ```roc
|
||||
## zipped = List.map2 ["a", "b", "c"] [1, 2, 3] Pair
|
||||
## ```
|
||||
map2 : List a, List b, (a, b -> c) -> List c
|
||||
|
@ -772,10 +771,10 @@ map4 : List a, List b, List c, List d, (a, b, c, d -> e) -> List e
|
|||
|
||||
## This works like [List.map], except it also passes the index
|
||||
## of the element to the conversion function.
|
||||
## ```
|
||||
## ```roc
|
||||
## 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
|
||||
|
@ -783,7 +782,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
|
||||
|
@ -797,23 +796,23 @@ mapWithIndexHelp = \src, dest, func, index, length ->
|
|||
## Returns a list of all the integers between `start` and `end`.
|
||||
##
|
||||
## To include the `start` and `end` integers themselves, use `At` like so:
|
||||
## ```
|
||||
## ```roc
|
||||
## List.range { start: At 2, end: At 5 } # returns [2, 3, 4, 5]
|
||||
## ```
|
||||
## To exclude them, use `After` and `Before`, like so:
|
||||
## ```
|
||||
## ```roc
|
||||
## List.range { start: After 2, end: Before 5 } # returns [3, 4]
|
||||
## ```
|
||||
## You can have the list end at a certain length rather than a certain integer:
|
||||
## ```
|
||||
## ```roc
|
||||
## List.range { start: At 6, end: Length 4 } # returns [6, 7, 8, 9]
|
||||
## ```
|
||||
## If `step` is specified, each integer increases by that much. (`step: 1` is the default.)
|
||||
## ```
|
||||
## ```roc
|
||||
## List.range { start: After 0, end: Before 9, step: 3 } # returns [3, 6]
|
||||
## ```
|
||||
## List.range will also generate a reversed list if step is negative or end comes before start:
|
||||
## ```
|
||||
## ```roc
|
||||
## List.range { start: At 5, end: At 2 } # returns [5, 4, 3, 2]
|
||||
## ```
|
||||
## All of these options are compatible with the others. For example, you can use `At` or `After`
|
||||
|
@ -944,21 +943,19 @@ expect
|
|||
## Sort with a custom comparison function
|
||||
sortWith : List a, (a, a -> [LT, EQ, GT]) -> List a
|
||||
|
||||
## Sorts a list in ascending order (lowest to highest), using a function which
|
||||
## specifies a way to represent each element as a number.
|
||||
## Sorts a list of numbers in ascending order (lowest to highest).
|
||||
##
|
||||
## To sort in descending order (highest to lowest), use [List.sortDesc] instead.
|
||||
sortAsc : List (Num a) -> List (Num a)
|
||||
sortAsc = \list -> List.sortWith list Num.compare
|
||||
|
||||
## Sorts a list in descending order (highest to lowest), using a function which
|
||||
## specifies a way to represent each element as a number.
|
||||
## Sorts a list of numbers in descending order (highest to lowest).
|
||||
##
|
||||
## To sort in ascending order (lowest to highest), use [List.sortAsc] instead.
|
||||
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]
|
||||
|
@ -968,12 +965,12 @@ first = \list ->
|
|||
Err _ -> Err ListWasEmpty
|
||||
|
||||
## Returns the given number of elements from the beginning of the list.
|
||||
## ```
|
||||
## ```roc
|
||||
## List.takeFirst [1, 2, 3, 4, 5, 6, 7, 8] 4
|
||||
## ```
|
||||
## If there are fewer elements in the list than the requested number,
|
||||
## returns the entire list.
|
||||
## ```
|
||||
## ```roc
|
||||
## List.takeFirst [1, 2] 5
|
||||
## ```
|
||||
## To *remove* elements from the beginning of the list, use `List.takeLast`.
|
||||
|
@ -983,17 +980,17 @@ 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 }
|
||||
|
||||
## Returns the given number of elements from the end of the list.
|
||||
## ```
|
||||
## ```roc
|
||||
## List.takeLast [1, 2, 3, 4, 5, 6, 7, 8] 4
|
||||
## ```
|
||||
## If there are fewer elements in the list than the requested number,
|
||||
## returns the entire list.
|
||||
## ```
|
||||
## ```roc
|
||||
## List.takeLast [1, 2] 5
|
||||
## ```
|
||||
## To *remove* elements from the end of the list, use `List.takeFirst`.
|
||||
|
@ -1003,19 +1000,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
|
||||
|
||||
|
@ -1026,7 +1023,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 ->
|
||||
|
@ -1101,7 +1098,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
|
||||
|
@ -1116,7 +1113,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
|
||||
|
@ -1134,26 +1131,26 @@ findLastIndex = \list, matches ->
|
|||
## including a total of `len` elements.
|
||||
##
|
||||
## If `start` is outside the bounds of the given list, returns the empty list.
|
||||
## ```
|
||||
## ```roc
|
||||
## List.sublist [1, 2, 3] { start: 4, len: 0 }
|
||||
## ```
|
||||
## If more elements are requested than exist in the list, returns as many as it can.
|
||||
## ```
|
||||
## ```roc
|
||||
## List.sublist [1, 2, 3, 4, 5] { start: 2, len: 10 }
|
||||
## ```
|
||||
## > If you want a sublist which goes all the way to the end of the list, no
|
||||
## > 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`
|
||||
## ```
|
||||
## ```roc
|
||||
## List.intersperse [1, 2, 3] 9 # [1, 9, 2, 9, 3]
|
||||
## ```
|
||||
intersperse : List elem, elem -> List elem
|
||||
|
@ -1202,7 +1199,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
|
||||
|
@ -1213,7 +1210,7 @@ split = \elements, userSplitIndex ->
|
|||
|
||||
## Returns the elements before the first occurrence of a delimiter, as well as the
|
||||
## remaining elements after that occurrence. If the delimiter is not found, returns `Err`.
|
||||
## ```
|
||||
## ```roc
|
||||
## List.splitFirst [Foo, Z, Bar, Z, Baz] Z == Ok { before: [Foo], after: [Bar, Z, Baz] }
|
||||
## ```
|
||||
splitFirst : List elem, elem -> Result { before : List elem, after : List elem } [NotFound] where elem implements Eq
|
||||
|
@ -1229,7 +1226,7 @@ splitFirst = \list, delimiter ->
|
|||
|
||||
## Returns the elements before the last occurrence of a delimiter, as well as the
|
||||
## remaining elements after that occurrence. If the delimiter is not found, returns `Err`.
|
||||
## ```
|
||||
## ```roc
|
||||
## List.splitLast [Foo, Z, Bar, Z, Baz] Z == Ok { before: [Foo, Z, Bar], after: [Baz] }
|
||||
## ```
|
||||
splitLast : List elem, elem -> Result { before : List elem, after : List elem } [NotFound] where elem implements Eq
|
||||
|
@ -1247,7 +1244,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
|
||||
[]
|
||||
|
@ -1255,7 +1252,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
|
||||
|
@ -1288,7 +1285,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
|
||||
|
@ -1303,7 +1300,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
|
||||
|
@ -1319,7 +1316,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
|
||||
|
|
|
@ -25,11 +25,9 @@ interface Num
|
|||
Unsigned32,
|
||||
Unsigned16,
|
||||
Unsigned8,
|
||||
Nat,
|
||||
Dec,
|
||||
F64,
|
||||
F32,
|
||||
Natural,
|
||||
Decimal,
|
||||
Binary32,
|
||||
Binary64,
|
||||
|
@ -98,10 +96,6 @@ interface Num
|
|||
mulSaturated,
|
||||
mulChecked,
|
||||
intCast,
|
||||
bytesToU16,
|
||||
bytesToU32,
|
||||
bytesToU64,
|
||||
bytesToU128,
|
||||
divCeil,
|
||||
divCeilChecked,
|
||||
divTrunc,
|
||||
|
@ -152,12 +146,16 @@ interface Num
|
|||
toU64Checked,
|
||||
toU128,
|
||||
toU128Checked,
|
||||
toNat,
|
||||
toNatChecked,
|
||||
toF32,
|
||||
toF32Checked,
|
||||
toF64,
|
||||
toF64Checked,
|
||||
withoutDecimalPoint,
|
||||
withDecimalPoint,
|
||||
f32ToParts,
|
||||
f64ToParts,
|
||||
f32FromParts,
|
||||
f64FromParts,
|
||||
]
|
||||
imports []
|
||||
|
||||
|
@ -167,7 +165,7 @@ import Result exposing [Result]
|
|||
## Represents a number that could be either an [Int] or a [Frac].
|
||||
##
|
||||
## This is useful for functions that can work on either, for example [Num.add], whose type is:
|
||||
## ```
|
||||
## ```roc
|
||||
## add : Num a, Num a -> Num a
|
||||
## ```
|
||||
## The number 1.5 technically has the type `Num (Fraction *)`, so when you pass
|
||||
|
@ -193,9 +191,9 @@ import Result exposing [Result]
|
|||
## 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
|
||||
|
@ -207,7 +205,7 @@ import Result exposing [Result]
|
|||
##
|
||||
## If this default of [I64] is not big enough for your purposes,
|
||||
## you can add an `i128` to the end of the number literal, like so:
|
||||
## ```
|
||||
## ```roc
|
||||
## Num.toStr 5_000_000_000i128
|
||||
## ```
|
||||
## This `i128` suffix specifies that you want this number literal to be
|
||||
|
@ -217,7 +215,6 @@ import Result exposing [Result]
|
|||
## * `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.
|
||||
|
@ -277,7 +274,7 @@ Num range := range
|
|||
##
|
||||
## You can optionally put underscores in your [Int] literals.
|
||||
## They have no effect on the number's value, but can make large numbers easier to read.
|
||||
## ```
|
||||
## ```roc
|
||||
## 1_000_000
|
||||
## ```
|
||||
## Integers come in two flavors: *signed* and *unsigned*.
|
||||
|
@ -327,16 +324,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.
|
||||
|
@ -354,14 +341,14 @@ Int range : Num (Integer range)
|
|||
##
|
||||
## If you don't specify a type, Roc will default to using [Dec] because it's
|
||||
## the least error-prone overall. For example, suppose you write this:
|
||||
## ```
|
||||
## ```roc
|
||||
## wasItPrecise = 0.1 + 0.2 == 0.3
|
||||
## ```
|
||||
## The value of `wasItPrecise` here will be `Bool.true`, because Roc uses [Dec]
|
||||
## by default when there are no types specified.
|
||||
##
|
||||
## In contrast, suppose we use `f32` or `f64` for one of these numbers:
|
||||
## ```
|
||||
## ```roc
|
||||
## wasItPrecise = 0.1f64 + 0.2 == 0.3
|
||||
## ```
|
||||
## Here, `wasItPrecise` will be `Bool.false` because the entire calculation will have
|
||||
|
@ -426,8 +413,6 @@ Unsigned32 := []
|
|||
Unsigned16 := []
|
||||
Unsigned8 := []
|
||||
|
||||
Natural := []
|
||||
|
||||
Integer range := range
|
||||
|
||||
I128 : Num (Integer Signed128)
|
||||
|
@ -444,18 +429,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 := []
|
||||
|
@ -561,11 +534,11 @@ tau = 2 * pi
|
|||
# ------- Functions
|
||||
## Convert a number to a [Str].
|
||||
##
|
||||
## ```
|
||||
## ```roc
|
||||
## Num.toStr 42
|
||||
## ```
|
||||
## Only [Frac] values will include a decimal point, and they will always include one.
|
||||
## ```
|
||||
## ```roc
|
||||
## Num.toStr 4.2
|
||||
## Num.toStr 4.0
|
||||
## ```
|
||||
|
@ -575,51 +548,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.
|
||||
|
@ -628,7 +556,7 @@ compare : Num a, Num a -> [LT, EQ, GT]
|
|||
##
|
||||
## 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).)
|
||||
## ```
|
||||
## ```roc
|
||||
## 5
|
||||
## |> Num.isLt 6
|
||||
## ```
|
||||
|
@ -640,7 +568,7 @@ isLt : Num a, Num a -> Bool
|
|||
##
|
||||
## 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).)
|
||||
## ```
|
||||
## ```roc
|
||||
## 6
|
||||
## |> Num.isGt 5
|
||||
## ```
|
||||
|
@ -666,17 +594,15 @@ isGte : Num a, Num a -> Bool
|
|||
##
|
||||
## A specific relative and absolute tolerance can be provided to change the threshold
|
||||
##
|
||||
## This function is symmetric: `Num.isApproxEq a b == Num.isApproxEq b a`
|
||||
##
|
||||
## 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
|
||||
isApproxEq = \x, y, { rtol ? 0.00001, atol ? 0.00000001 } ->
|
||||
eq = x <= y && x >= y
|
||||
meetsTolerance = Num.absDiff x y <= Num.max atol (rtol * Num.max (Num.abs x) (Num.abs y))
|
||||
eq || meetsTolerance
|
||||
|
||||
## Returns `Bool.true` if the number is `0`, and `Bool.false` otherwise.
|
||||
isZero : Num a -> Bool
|
||||
|
@ -705,14 +631,14 @@ toFrac : Num * -> Frac *
|
|||
|
||||
## Returns `Bool.true` if the [Frac] is not a number as defined by [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754)
|
||||
##
|
||||
## ```
|
||||
## ```roc
|
||||
## Num.isNaN (0 / 0)
|
||||
## ```
|
||||
isNaN : Frac * -> Bool
|
||||
|
||||
## Returns `Bool.true` if the [Frac] is positive or negative infinity as defined by [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754)
|
||||
##
|
||||
## ```
|
||||
## ```roc
|
||||
## Num.isInfinite (1 / 0)
|
||||
##
|
||||
## Num.isInfinite (-1 / 0)
|
||||
|
@ -721,7 +647,7 @@ isInfinite : Frac * -> Bool
|
|||
|
||||
## Returns `Bool.true` if the [Frac] is not an infinity as defined by [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754)
|
||||
##
|
||||
## ```
|
||||
## ```roc
|
||||
## Num.isFinite 42
|
||||
## ```
|
||||
isFinite : Frac * -> Bool
|
||||
|
@ -731,7 +657,7 @@ isFinite : Frac * -> Bool
|
|||
## * For a positive number, returns the same number.
|
||||
## * For a negative number, returns the same number except positive.
|
||||
## * For zero, returns zero.
|
||||
## ```
|
||||
## ```roc
|
||||
## Num.abs 4
|
||||
##
|
||||
## Num.abs -2.5
|
||||
|
@ -751,7 +677,7 @@ abs : Num a -> Num a
|
|||
|
||||
## Returns the absolute difference between two numbers.
|
||||
##
|
||||
## ```
|
||||
## ```roc
|
||||
## Num.absDiff 5 3
|
||||
##
|
||||
## Num.absDiff -3 5
|
||||
|
@ -771,7 +697,7 @@ absDiff = \a, b ->
|
|||
b - a
|
||||
|
||||
## Returns a negative number when given a positive one, and vice versa.
|
||||
## ```
|
||||
## ```roc
|
||||
## Num.neg 5
|
||||
##
|
||||
## Num.neg -2.5
|
||||
|
@ -796,13 +722,13 @@ neg : Num a -> Num a
|
|||
## (To add an [Int] and a [Frac], first convert one so that they both have the same type. There are functions in this module that can convert both [Int] to [Frac] and the other way around.)
|
||||
##
|
||||
## `a + b` is shorthand for `Num.add a b`.
|
||||
## ```
|
||||
## ```roc
|
||||
## 5 + 7
|
||||
##
|
||||
## Num.add 5 7
|
||||
## ```
|
||||
## `Num.add` can be convenient in pipelines.
|
||||
## ```
|
||||
## ```roc
|
||||
## Frac.pi
|
||||
## |> Num.add 1.0
|
||||
## ```
|
||||
|
@ -817,13 +743,13 @@ add : Num a, Num a -> Num a
|
|||
## (To subtract an [Int] and a [Frac], first convert one so that they both have the same type. There are functions in this module that can convert both [Int] to [Frac] and the other way around.)
|
||||
##
|
||||
## `a - b` is shorthand for `Num.sub a b`.
|
||||
## ```
|
||||
## ```roc
|
||||
## 7 - 5
|
||||
##
|
||||
## Num.sub 7 5
|
||||
## ```
|
||||
## `Num.sub` can be convenient in pipelines.
|
||||
## ```
|
||||
## ```roc
|
||||
## Frac.pi
|
||||
## |> Num.sub 2.0
|
||||
## ```
|
||||
|
@ -838,7 +764,7 @@ sub : Num a, Num a -> Num a
|
|||
## (To multiply an [Int] and a [Frac], first convert one so that they both have the same type. There are functions in this module that can convert both [Int] to [Frac] and the other way around.)
|
||||
##
|
||||
## `a * b` is shorthand for `Num.mul a b`.
|
||||
## ```
|
||||
## ```roc
|
||||
## 5 * 7
|
||||
##
|
||||
## Num.mul 5 7
|
||||
|
@ -846,7 +772,7 @@ sub : Num a, Num a -> Num a
|
|||
##
|
||||
## `Num.mul` can be convenient in pipelines.
|
||||
##
|
||||
## ```
|
||||
## ```roc
|
||||
## Frac.pi
|
||||
## |> Num.mul 2.0
|
||||
## ```
|
||||
|
@ -858,7 +784,7 @@ mul : Num a, Num a -> Num a
|
|||
|
||||
## Obtains the smaller between two numbers of the same type.
|
||||
##
|
||||
## ```
|
||||
## ```roc
|
||||
## Num.min 100 0
|
||||
##
|
||||
## Num.min 3.0 -3.0
|
||||
|
@ -872,7 +798,7 @@ min = \a, b ->
|
|||
|
||||
## Obtains the greater between two numbers of the same type.
|
||||
##
|
||||
## ```
|
||||
## ```roc
|
||||
## Num.max 100 0
|
||||
##
|
||||
## Num.max 3.0 -3.0
|
||||
|
@ -908,7 +834,7 @@ atan : Frac a -> Frac a
|
|||
## > this standard, deviating from these rules has a significant performance
|
||||
## > cost! Since the most common reason to choose [F64] or [F32] over [Dec] is
|
||||
## > access to hardware-accelerated performance, Roc follows these rules exactly.
|
||||
## ```
|
||||
## ```roc
|
||||
## Num.sqrt 4.0
|
||||
##
|
||||
## Num.sqrt 1.5
|
||||
|
@ -957,13 +883,13 @@ logChecked = \x ->
|
|||
##
|
||||
## To divide an [Int] and a [Frac], first convert the [Int] to a [Frac] using
|
||||
## one of the functions in this module like #toDec.
|
||||
## ```
|
||||
## ```roc
|
||||
## 5.0 / 7.0
|
||||
##
|
||||
## Num.div 5 7
|
||||
## ```
|
||||
## `Num.div` can be convenient in pipelines.
|
||||
## ```
|
||||
## ```roc
|
||||
## Num.pi
|
||||
## |> Num.div 2.0
|
||||
## ```
|
||||
|
@ -992,7 +918,7 @@ divCeilChecked = \a, b ->
|
|||
## Division by zero is undefined in mathematics. As such, you should make
|
||||
## sure never to pass zero as the denominator to this function! If you do,
|
||||
## it will crash.
|
||||
## ```
|
||||
## ```roc
|
||||
## 5 // 7
|
||||
##
|
||||
## Num.divTrunc 5 7
|
||||
|
@ -1002,18 +928,26 @@ 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.
|
||||
##
|
||||
## `a % b` is shorthand for `Num.rem a b`.
|
||||
## ```
|
||||
## ```roc
|
||||
## 5 % 7
|
||||
##
|
||||
## Num.rem 5 7
|
||||
|
@ -1023,13 +957,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
|
||||
|
||||
|
@ -1056,10 +998,10 @@ bitwiseNot = \n ->
|
|||
##
|
||||
## The least significant bits always become 0. This means that shifting left is
|
||||
## like multiplying by factors of two for unsigned integers.
|
||||
## ```
|
||||
## ```roc
|
||||
## shiftLeftBy 0b0000_0011 2 == 0b0000_1100
|
||||
##
|
||||
## 0b0000_0101 |> shiftLeftBy 2 == 0b0000_1100
|
||||
## 0b0000_0101 |> shiftLeftBy 2 == 0b0001_0100
|
||||
## ```
|
||||
## In some languages `shiftLeftBy` is implemented as a binary operator `<<`.
|
||||
shiftLeftBy : Int a, U8 -> Int a
|
||||
|
@ -1067,7 +1009,7 @@ shiftLeftBy : Int a, U8 -> Int a
|
|||
## Bitwise arithmetic shift of a number by another
|
||||
##
|
||||
## The most significant bits are copied from the current.
|
||||
## ```
|
||||
## ```roc
|
||||
## shiftRightBy 0b0000_1100 2 == 0b0000_0011
|
||||
##
|
||||
## 0b0001_0100 |> shiftRightBy 2 == 0b0000_0101
|
||||
|
@ -1081,7 +1023,7 @@ shiftRightBy : Int a, U8 -> Int a
|
|||
##
|
||||
## The most significant bits always become 0. This means that shifting right is
|
||||
## like dividing by factors of two for unsigned integers.
|
||||
## ```
|
||||
## ```roc
|
||||
## shiftRightZfBy 0b0010_1000 2 == 0b0000_1010
|
||||
##
|
||||
## 0b0010_1000 |> shiftRightZfBy 2 == 0b0000_1010
|
||||
|
@ -1117,7 +1059,7 @@ powInt : Int a, Int a -> Int a
|
|||
|
||||
## Counts the number of most-significant (leading in a big-Endian sense) zeroes in an integer.
|
||||
##
|
||||
## ```
|
||||
## ```roc
|
||||
## Num.countLeadingZeroBits 0b0001_1100u8
|
||||
##
|
||||
## 3
|
||||
|
@ -1130,7 +1072,7 @@ countLeadingZeroBits : Int a -> U8
|
|||
|
||||
## Counts the number of least-significant (trailing in a big-Endian sense) zeroes in an integer.
|
||||
##
|
||||
## ```
|
||||
## ```roc
|
||||
## Num.countTrailingZeroBits 0b0001_1100u8
|
||||
##
|
||||
## 2
|
||||
|
@ -1143,7 +1085,7 @@ countTrailingZeroBits : Int a -> U8
|
|||
|
||||
## Counts the number of set bits in an integer.
|
||||
##
|
||||
## ```
|
||||
## ```roc
|
||||
## Num.countOneBits 0b0001_1100u8
|
||||
##
|
||||
## 3
|
||||
|
@ -1447,22 +1389,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
|
||||
|
@ -1484,6 +1410,28 @@ 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]
|
||||
|
||||
## Turns a [Dec] into its [I128] representation by removing the decimal point.
|
||||
## This is equivalent to multiplying the [Dec] by 10^18.
|
||||
withoutDecimalPoint : Dec -> I128
|
||||
|
||||
## Turns a [I128] into the coresponding [Dec] by adding the decimal point.
|
||||
## This is equivalent to dividing the [I128] by 10^18.
|
||||
withDecimalPoint : I128 -> Dec
|
||||
|
||||
## Splits a [F32] into its components according to IEEE 754 standard.
|
||||
f32ToParts : F32 -> { sign : Bool, exponent : U8, fraction : U32 }
|
||||
|
||||
## Splits a [F64] into its components according to IEEE 754 standard.
|
||||
f64ToParts : F64 -> { sign : Bool, exponent : U16, fraction : U64 }
|
||||
|
||||
## Combine parts of a [F32] according to IEEE 754 standard.
|
||||
## The fraction should not be bigger than 0x007F_FFFF, any bigger value will be truncated.
|
||||
f32FromParts : { sign : Bool, exponent : U8, fraction : U32 } -> F32
|
||||
|
||||
## Combine parts of a [F64] according to IEEE 754 standard.
|
||||
## The fraction should not be bigger than 0x000F_FFFF_FFFF_FFFF, any bigger value will be truncated.
|
||||
## The exponent should not be bigger than 0x07FF, any bigger value will be truncated.
|
||||
f64FromParts : { sign : Bool, exponent : U16, fraction : U64 } -> F64
|
||||
|
|
|
@ -9,7 +9,7 @@ import Bool exposing [Bool]
|
|||
Result ok err : [Ok ok, Err err]
|
||||
|
||||
## Returns `Bool.true` if the result indicates a success, else returns `Bool.false`
|
||||
## ```
|
||||
## ```roc
|
||||
## Result.isOk (Ok 5)
|
||||
## ```
|
||||
isOk : Result ok err -> Bool
|
||||
|
@ -19,7 +19,7 @@ isOk = \result ->
|
|||
Err _ -> Bool.false
|
||||
|
||||
## Returns `Bool.true` if the result indicates a failure, else returns `Bool.false`
|
||||
## ```
|
||||
## ```roc
|
||||
## Result.isErr (Err "uh oh")
|
||||
## ```
|
||||
isErr : Result ok err -> Bool
|
||||
|
@ -30,7 +30,7 @@ isErr = \result ->
|
|||
|
||||
## If the result is `Ok`, returns the value it holds. Otherwise, returns
|
||||
## the given default value.
|
||||
## ```
|
||||
## ```roc
|
||||
## Result.withDefault (Ok 7) 42
|
||||
## Result.withDefault (Err "uh oh") 42
|
||||
## ```
|
||||
|
@ -43,7 +43,7 @@ withDefault = \result, default ->
|
|||
## If the result is `Ok`, transforms the value it holds by running a conversion
|
||||
## function on it. Then returns a new `Ok` holding the transformed value. If the
|
||||
## result is `Err`, this has no effect. Use [mapErr] to transform an `Err`.
|
||||
## ```
|
||||
## ```roc
|
||||
## Result.map (Ok 12) Num.neg
|
||||
## Result.map (Err "yipes!") Num.neg
|
||||
## ```
|
||||
|
@ -59,7 +59,7 @@ map = \result, transform ->
|
|||
## If the result is `Err`, transforms the value it holds by running a conversion
|
||||
## function on it. Then returns a new `Err` holding the transformed value. If
|
||||
## the result is `Ok`, this has no effect. Use [map] to transform an `Ok`.
|
||||
## ```
|
||||
## ```roc
|
||||
## Result.mapErr (Err "yipes!") Str.isEmpty
|
||||
## Result.mapErr (Ok 12) Str.isEmpty
|
||||
## ```
|
||||
|
@ -72,7 +72,7 @@ mapErr = \result, transform ->
|
|||
## If the result is `Ok`, transforms the entire result by running a conversion
|
||||
## function on the value the `Ok` holds. Then returns that new result. If the
|
||||
## result is `Err`, this has no effect. Use `onErr` to transform an `Err`.
|
||||
## ```
|
||||
## ```roc
|
||||
## Result.try (Ok -1) \num -> if num < 0 then Err "negative!" else Ok -num
|
||||
## Result.try (Err "yipes!") \num -> if num < 0 then Err "negative!" else Ok -num
|
||||
## ```
|
||||
|
@ -85,9 +85,9 @@ try = \result, transform ->
|
|||
## If the result is `Err`, transforms the entire result by running a conversion
|
||||
## 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
|
||||
## ```roc
|
||||
## 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 ->
|
||||
|
|
|
@ -29,7 +29,7 @@ interface Set
|
|||
import List
|
||||
import Bool exposing [Bool, Eq]
|
||||
import Dict
|
||||
import Num exposing [Nat]
|
||||
import Num exposing [U64]
|
||||
import Hash exposing [Hash, Hasher]
|
||||
import Inspect exposing [Inspect, Inspector, InspectFormatter]
|
||||
|
||||
|
@ -68,7 +68,7 @@ toInspectorSet = \set ->
|
|||
Inspect.apply (Inspect.set set walk Inspect.toInspector) fmt
|
||||
|
||||
## Creates a new empty `Set`.
|
||||
## ```
|
||||
## ```roc
|
||||
## emptySet = Set.empty {}
|
||||
## countValues = Set.len emptySet
|
||||
##
|
||||
|
@ -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)
|
||||
|
||||
|
@ -97,7 +97,7 @@ releaseExcessCapacity = \@Set dict ->
|
|||
@Set (Dict.releaseExcessCapacity dict)
|
||||
|
||||
## Creates a new `Set` with a single value.
|
||||
## ```
|
||||
## ```roc
|
||||
## singleItemSet = Set.single "Apple"
|
||||
## countValues = Set.len singleItemSet
|
||||
##
|
||||
|
@ -108,7 +108,7 @@ single = \key ->
|
|||
Dict.single key {} |> @Set
|
||||
|
||||
## Insert a value into a `Set`.
|
||||
## ```
|
||||
## ```roc
|
||||
## fewItemSet =
|
||||
## Set.empty {}
|
||||
## |> Set.insert "Apple"
|
||||
|
@ -141,7 +141,7 @@ expect
|
|||
expected == actual
|
||||
|
||||
## Counts the number of values in a given `Set`.
|
||||
## ```
|
||||
## ```roc
|
||||
## fewItemSet =
|
||||
## Set.empty {}
|
||||
## |> Set.insert "Apple"
|
||||
|
@ -152,24 +152,24 @@ expect
|
|||
##
|
||||
## expect countValues == 3
|
||||
## ```
|
||||
len : Set * -> Nat
|
||||
len : Set * -> U64
|
||||
len = \@Set dict ->
|
||||
Dict.len dict
|
||||
|
||||
## Returns the max number of elements the set can hold before requiring a rehash.
|
||||
## ```
|
||||
## ```roc
|
||||
## foodSet =
|
||||
## Set.empty {}
|
||||
## |> Set.insert "apple"
|
||||
##
|
||||
## capacityOfSet = Set.capacity foodSet
|
||||
## ```
|
||||
capacity : Set * -> Nat
|
||||
capacity : Set * -> U64
|
||||
capacity = \@Set dict ->
|
||||
Dict.capacity dict
|
||||
|
||||
## Check if the set is empty.
|
||||
## ```
|
||||
## ```roc
|
||||
## Set.isEmpty (Set.empty {} |> Set.insert 42)
|
||||
##
|
||||
## Set.isEmpty (Set.empty {})
|
||||
|
@ -191,7 +191,7 @@ expect
|
|||
actual == 3
|
||||
|
||||
## Removes the value from the given `Set`.
|
||||
## ```
|
||||
## ```roc
|
||||
## numbers =
|
||||
## Set.empty {}
|
||||
## |> Set.insert 10
|
||||
|
@ -209,7 +209,7 @@ remove = \@Set dict, key ->
|
|||
Dict.remove dict key |> @Set
|
||||
|
||||
## Test if a value is in the `Set`.
|
||||
## ```
|
||||
## ```roc
|
||||
## Fruit : [Apple, Pear, Banana]
|
||||
##
|
||||
## fruit : Set Fruit
|
||||
|
@ -228,7 +228,7 @@ contains = \@Set dict, key ->
|
|||
Dict.contains dict key
|
||||
|
||||
## Retrieve the values in a `Set` as a `List`.
|
||||
## ```
|
||||
## ```roc
|
||||
## numbers : Set U64
|
||||
## numbers = Set.fromList [1,2,3,4,5]
|
||||
##
|
||||
|
@ -241,7 +241,7 @@ toList = \@Set dict ->
|
|||
Dict.keys dict
|
||||
|
||||
## Create a `Set` from a `List` of values.
|
||||
## ```
|
||||
## ```roc
|
||||
## values =
|
||||
## Set.empty {}
|
||||
## |> Set.insert Banana
|
||||
|
@ -261,7 +261,7 @@ fromList = \list ->
|
|||
## [union](https://en.wikipedia.org/wiki/Union_(set_theory))
|
||||
## of all the values pairs. This means that all of the values in both `Set`s
|
||||
## will be combined.
|
||||
## ```
|
||||
## ```roc
|
||||
## set1 = Set.single Left
|
||||
## set2 = Set.single Right
|
||||
##
|
||||
|
@ -274,7 +274,7 @@ union = \@Set dict1, @Set dict2 ->
|
|||
## Combine two `Set`s by keeping the [intersection](https://en.wikipedia.org/wiki/Intersection_(set_theory))
|
||||
## of all the values pairs. This means that we keep only those values that are
|
||||
## in both `Set`s.
|
||||
## ```
|
||||
## ```roc
|
||||
## set1 = Set.fromList [Left, Other]
|
||||
## set2 = Set.fromList [Left, Right]
|
||||
##
|
||||
|
@ -288,7 +288,7 @@ intersection = \@Set dict1, @Set dict2 ->
|
|||
## using the [set difference](https://en.wikipedia.org/wiki/Complement_(set_theory)#Relative_complement)
|
||||
## of the values. This means that we will be left with only those values that
|
||||
## are in the first and not in the second.
|
||||
## ```
|
||||
## ```roc
|
||||
## first = Set.fromList [Left, Right, Up, Down]
|
||||
## second = Set.fromList [Left, Right]
|
||||
##
|
||||
|
@ -299,7 +299,7 @@ difference = \@Set dict1, @Set dict2 ->
|
|||
Dict.removeAll dict1 dict2 |> @Set
|
||||
|
||||
## Iterate through the values of a given `Set` and build a value.
|
||||
## ```
|
||||
## ```roc
|
||||
## values = Set.fromList ["March", "April", "May"]
|
||||
##
|
||||
## startsWithLetterM = \month ->
|
||||
|
@ -345,7 +345,7 @@ joinMap = \set, transform ->
|
|||
|
||||
## Iterate through the values of a given `Set` and build a value, can stop
|
||||
## iterating part way through the collection.
|
||||
## ```
|
||||
## ```roc
|
||||
## numbers = Set.fromList [1,2,3,4,5,6,42,7,8,9,10]
|
||||
##
|
||||
## find42 = \state, k ->
|
||||
|
@ -364,7 +364,7 @@ walkUntil = \@Set dict, state, step ->
|
|||
|
||||
## Run the given function on each element in the `Set`, and return
|
||||
## a `Set` with just the elements for which the function returned `Bool.true`.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Set.fromList [1,2,3,4,5]
|
||||
## |> Set.keepIf \k -> k >= 3
|
||||
## |> Bool.isEq (Set.fromList [3,4,5])
|
||||
|
@ -375,7 +375,7 @@ keepIf = \@Set dict, predicate ->
|
|||
|
||||
## Run the given function on each element in the `Set`, and return
|
||||
## a `Set` with just the elements for which the function returned `Bool.false`.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Set.fromList [1,2,3,4,5]
|
||||
## |> Set.dropIf \k -> k >= 3
|
||||
## |> Bool.isEq (Set.fromList [1,2])
|
||||
|
@ -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
|
||||
|
|
|
@ -338,7 +338,6 @@ interface Str
|
|||
countUtf8Bytes,
|
||||
toUtf8,
|
||||
fromUtf8,
|
||||
fromUtf8Range,
|
||||
startsWith,
|
||||
endsWith,
|
||||
trim,
|
||||
|
@ -347,7 +346,6 @@ interface Str
|
|||
toDec,
|
||||
toF64,
|
||||
toF32,
|
||||
toNat,
|
||||
toU128,
|
||||
toI128,
|
||||
toU64,
|
||||
|
@ -376,7 +374,7 @@ interface Str
|
|||
import Bool exposing [Bool]
|
||||
import Result exposing [Result]
|
||||
import List
|
||||
import Num exposing [Nat, Num, U8, U16, U32, U64, U128, I8, I16, I32, I64, I128, F32, F64, Dec]
|
||||
import Num exposing [Num, U8, U16, U32, U64, U128, I8, I16, I32, I64, I128, F32, F64, Dec]
|
||||
|
||||
Utf8ByteProblem : [
|
||||
InvalidStartByte,
|
||||
|
@ -387,17 +385,17 @@ Utf8ByteProblem : [
|
|||
EncodesSurrogateHalf,
|
||||
]
|
||||
|
||||
Utf8Problem : { byteIndex : Nat, problem : Utf8ByteProblem }
|
||||
Utf8Problem : { byteIndex : U64, problem : Utf8ByteProblem }
|
||||
|
||||
## Returns [Bool.true] if the string is empty, and [Bool.false] otherwise.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.isEmpty "hi!" == Bool.false
|
||||
## expect Str.isEmpty "" == Bool.true
|
||||
## ```
|
||||
isEmpty : Str -> Bool
|
||||
|
||||
## Concatenates two strings together.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.concat "ab" "cd" == "abcd"
|
||||
## expect Str.concat "hello" "" == "hello"
|
||||
## expect Str.concat "" "" == ""
|
||||
|
@ -409,7 +407,7 @@ concat : Str, Str -> Str
|
|||
## This is a performance optimization tool that's like calling [Str.reserve] on an empty string.
|
||||
## It's useful when you plan to build up a string incrementally, for example by calling [Str.concat] on it:
|
||||
##
|
||||
## ```
|
||||
## ```roc
|
||||
## greeting = "Hello and welcome to Roc"
|
||||
## subject = "Awesome Programmer"
|
||||
##
|
||||
|
@ -428,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.
|
||||
##
|
||||
|
@ -436,7 +434,7 @@ withCapacity : Nat -> Str
|
|||
## allocating extra capacity up front, which can prevent the need for reallocations and copies.
|
||||
## Consider the following example which does not use [Str.reserve]:
|
||||
##
|
||||
## ```
|
||||
## ```roc
|
||||
## greeting = "Hello and welcome to Roc"
|
||||
## subject = "Awesome Programmer"
|
||||
##
|
||||
|
@ -458,7 +456,7 @@ withCapacity : Nat -> Str
|
|||
##
|
||||
## Here's a modified example which uses [Str.reserve] to eliminate the need for all that reallocation, copying, and deallocation.
|
||||
##
|
||||
## ```
|
||||
## ```roc
|
||||
## helloWorld =
|
||||
## greeting
|
||||
## |> Str.reserve 21
|
||||
|
@ -486,11 +484,11 @@ 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.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.joinWith ["one", "two", "three"] ", " == "one, two, three"
|
||||
## expect Str.joinWith ["1", "2", "3", "4"] "." == "1.2.3.4"
|
||||
## ```
|
||||
|
@ -500,28 +498,28 @@ joinWith : List Str, Str -> Str
|
|||
##
|
||||
## Passing `""` for the separator is not useful;
|
||||
## it returns the original string wrapped in a [List].
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.split "1,2,3" "," == ["1","2","3"]
|
||||
## expect Str.split "1,2,3" "" == ["1,2,3"]
|
||||
## ```
|
||||
split : Str, Str -> List Str
|
||||
|
||||
## Repeats a string the given number of times.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.repeat "z" 3 == "zzz"
|
||||
## expect Str.repeat "na" 8 == "nananananananana"
|
||||
## ```
|
||||
## Returns `""` when given `""` for the string or `0` for the count.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.repeat "" 10 == ""
|
||||
## expect Str.repeat "anything" 0 == ""
|
||||
## ```
|
||||
repeat : Str, Nat -> Str
|
||||
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,
|
||||
## see [Str.split].)
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.toUtf8 "Roc" == [82, 111, 99]
|
||||
## expect Str.toUtf8 "鹏" == [233, 185, 143]
|
||||
## expect Str.toUtf8 "சி" == [224, 174, 154, 224, 174, 191]
|
||||
|
@ -532,7 +530,7 @@ toUtf8 : Str -> List U8
|
|||
## Converts a [List] of [U8] UTF-8 [code units](https://unicode.org/glossary/#code_unit) to a string.
|
||||
##
|
||||
## Returns `Err` if the given bytes are invalid UTF-8, and returns `Ok ""` when given `[]`.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.fromUtf8 [82, 111, 99] == Ok "Roc"
|
||||
## expect Str.fromUtf8 [233, 185, 143] == Ok "鹏"
|
||||
## expect Str.fromUtf8 [224, 174, 154, 224, 174, 191] == Ok "சி"
|
||||
|
@ -540,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
|
||||
|
@ -555,47 +553,24 @@ 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.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.startsWith "ABC" "A" == Bool.true
|
||||
## expect Str.startsWith "ABC" "X" == Bool.false
|
||||
## ```
|
||||
startsWith : Str, Str -> Bool
|
||||
|
||||
## Check if the given [Str] ends with a value.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.endsWith "ABC" "C" == Bool.true
|
||||
## expect Str.endsWith "ABC" "X" == Bool.false
|
||||
## ```
|
||||
|
@ -603,26 +578,26 @@ endsWith : Str, Str -> Bool
|
|||
|
||||
## Return the [Str] with all whitespace removed from both the beginning
|
||||
## as well as the end.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.trim " Hello \n\n" == "Hello"
|
||||
## ```
|
||||
trim : Str -> Str
|
||||
|
||||
## Return the [Str] with all whitespace removed from the beginning.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.trimStart " Hello \n\n" == "Hello \n\n"
|
||||
## ```
|
||||
trimStart : Str -> Str
|
||||
|
||||
## Return the [Str] with all whitespace removed from the end.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.trimEnd " Hello \n\n" == " Hello"
|
||||
## ```
|
||||
trimEnd : Str -> Str
|
||||
|
||||
## Encode a [Str] to a [Dec]. A [Dec] value is a 128-bit decimal
|
||||
## [fixed-point number](https://en.wikipedia.org/wiki/Fixed-point_arithmetic).
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.toDec "10" == Ok 10dec
|
||||
## expect Str.toDec "-0.25" == Ok -0.25dec
|
||||
## expect Str.toDec "not a number" == Err InvalidNumStr
|
||||
|
@ -633,7 +608,7 @@ toDec = \string -> strToNumHelp string
|
|||
## Encode a [Str] to a [F64]. A [F64] value is a 64-bit
|
||||
## [floating-point number](https://en.wikipedia.org/wiki/IEEE_754) and can be
|
||||
## specified with a `f64` suffix.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.toF64 "0.10" == Ok 0.10f64
|
||||
## expect Str.toF64 "not a number" == Err InvalidNumStr
|
||||
## ```
|
||||
|
@ -643,36 +618,17 @@ toF64 = \string -> strToNumHelp string
|
|||
## Encode a [Str] to a [F32].A [F32] value is a 32-bit
|
||||
## [floating-point number](https://en.wikipedia.org/wiki/IEEE_754) and can be
|
||||
## specified with a `f32` suffix.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.toF32 "0.10" == Ok 0.10f32
|
||||
## expect Str.toF32 "not a number" == Err InvalidNumStr
|
||||
## ```
|
||||
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.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.toU128 "1500" == Ok 1500u128
|
||||
## expect Str.toU128 "0.1" == Err InvalidNumStr
|
||||
## expect Str.toU128 "-1" == Err InvalidNumStr
|
||||
|
@ -685,7 +641,7 @@ toU128 = \string -> strToNumHelp string
|
|||
## from `-170_141_183_460_469_231_731_687_303_715_884_105_728` to
|
||||
## `170_141_183_460_469_231_731_687_303_715_884_105_727`. It can be specified
|
||||
## with a i128 suffix.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.toI128 "1500" == Ok 1500i128
|
||||
## expect Str.toI128 "-1" == Ok -1i128
|
||||
## expect Str.toI128 "0.1" == Err InvalidNumStr
|
||||
|
@ -697,7 +653,7 @@ toI128 = \string -> strToNumHelp string
|
|||
## Encode a [Str] to an unsigned [U64] integer. A [U64] value can hold numbers
|
||||
## from `0` to `18_446_744_073_709_551_615` (over 18 quintillion). It
|
||||
## can be specified with a u64 suffix.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.toU64 "1500" == Ok 1500u64
|
||||
## expect Str.toU64 "0.1" == Err InvalidNumStr
|
||||
## expect Str.toU64 "-1" == Err InvalidNumStr
|
||||
|
@ -709,7 +665,7 @@ toU64 = \string -> strToNumHelp string
|
|||
## Encode a [Str] to a signed [I64] integer. A [I64] value can hold numbers
|
||||
## from `-9_223_372_036_854_775_808` to `9_223_372_036_854_775_807`. It can be
|
||||
## specified with a i64 suffix.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.toI64 "1500" == Ok 1500i64
|
||||
## expect Str.toI64 "-1" == Ok -1i64
|
||||
## expect Str.toI64 "0.1" == Err InvalidNumStr
|
||||
|
@ -721,7 +677,7 @@ toI64 = \string -> strToNumHelp string
|
|||
## Encode a [Str] to an unsigned [U32] integer. A [U32] value can hold numbers
|
||||
## from `0` to `4_294_967_295` (over 4 billion). It can be specified with
|
||||
## a u32 suffix.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.toU32 "1500" == Ok 1500u32
|
||||
## expect Str.toU32 "0.1" == Err InvalidNumStr
|
||||
## expect Str.toU32 "-1" == Err InvalidNumStr
|
||||
|
@ -733,7 +689,7 @@ toU32 = \string -> strToNumHelp string
|
|||
## Encode a [Str] to a signed [I32] integer. A [I32] value can hold numbers
|
||||
## from `-2_147_483_648` to `2_147_483_647`. It can be
|
||||
## specified with a i32 suffix.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.toI32 "1500" == Ok 1500i32
|
||||
## expect Str.toI32 "-1" == Ok -1i32
|
||||
## expect Str.toI32 "0.1" == Err InvalidNumStr
|
||||
|
@ -744,7 +700,7 @@ toI32 = \string -> strToNumHelp string
|
|||
|
||||
## Encode a [Str] to an unsigned [U16] integer. A [U16] value can hold numbers
|
||||
## from `0` to `65_535`. It can be specified with a u16 suffix.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.toU16 "1500" == Ok 1500u16
|
||||
## expect Str.toU16 "0.1" == Err InvalidNumStr
|
||||
## expect Str.toU16 "-1" == Err InvalidNumStr
|
||||
|
@ -756,7 +712,7 @@ toU16 = \string -> strToNumHelp string
|
|||
## Encode a [Str] to a signed [I16] integer. A [I16] value can hold numbers
|
||||
## from `-32_768` to `32_767`. It can be
|
||||
## specified with a i16 suffix.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.toI16 "1500" == Ok 1500i16
|
||||
## expect Str.toI16 "-1" == Ok -1i16
|
||||
## expect Str.toI16 "0.1" == Err InvalidNumStr
|
||||
|
@ -767,7 +723,7 @@ toI16 = \string -> strToNumHelp string
|
|||
|
||||
## Encode a [Str] to an unsigned [U8] integer. A [U8] value can hold numbers
|
||||
## from `0` to `255`. It can be specified with a u8 suffix.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.toU8 "250" == Ok 250u8
|
||||
## expect Str.toU8 "-0.1" == Err InvalidNumStr
|
||||
## expect Str.toU8 "not a number" == Err InvalidNumStr
|
||||
|
@ -779,7 +735,7 @@ toU8 = \string -> strToNumHelp string
|
|||
## Encode a [Str] to a signed [I8] integer. A [I8] value can hold numbers
|
||||
## from `-128` to `127`. It can be
|
||||
## specified with a i8 suffix.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.toI8 "-15" == Ok -15i8
|
||||
## expect Str.toI8 "150.00" == Err InvalidNumStr
|
||||
## expect Str.toI8 "not a number" == Err InvalidNumStr
|
||||
|
@ -788,21 +744,21 @@ 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.
|
||||
## ```
|
||||
## ```roc
|
||||
## 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.
|
||||
##
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.replaceEach "foo/bar/baz" "/" "_" == "foo_bar_baz"
|
||||
## expect Str.replaceEach "not here" "/" "_" == "not here"
|
||||
## ```
|
||||
|
@ -836,7 +792,7 @@ expect Str.replaceEach "abcdefg" "nothing" "_" == "abcdefg"
|
|||
## Returns the given [Str] with the first occurrence of a substring replaced.
|
||||
## If the substring is not found, returns the original string.
|
||||
##
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.replaceFirst "foo/bar/baz" "/" "_" == "foo_bar/baz"
|
||||
## expect Str.replaceFirst "no slashes here" "/" "_" == "no slashes here"
|
||||
## ```
|
||||
|
@ -844,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
|
||||
|
||||
|
@ -854,7 +810,7 @@ expect Str.replaceFirst "abcdefg" "nothing" "_" == "abcdefg"
|
|||
## Returns the given [Str] with the last occurrence of a substring replaced.
|
||||
## If the substring is not found, returns the original string.
|
||||
##
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.replaceLast "foo/bar/baz" "/" "_" == "foo/bar_baz"
|
||||
## expect Str.replaceLast "no slashes here" "/" "_" == "no slashes here"
|
||||
## ```
|
||||
|
@ -862,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
|
||||
|
||||
|
@ -872,7 +828,7 @@ expect Str.replaceLast "abcdefg" "nothing" "_" == "abcdefg"
|
|||
## Returns the given [Str] before the first occurrence of a [delimiter](https://www.computerhope.com/jargon/d/delimite.htm), as well
|
||||
## as the rest of the string after that occurrence.
|
||||
## Returns [Err NotFound] if the delimiter is not found.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.splitFirst "foo/bar/baz" "/" == Ok { before: "foo", after: "bar/baz" }
|
||||
## expect Str.splitFirst "no slashes here" "/" == Err NotFound
|
||||
## ```
|
||||
|
@ -905,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
|
||||
|
@ -913,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
|
||||
|
@ -926,7 +882,7 @@ firstMatchHelp = \haystack, needle, index, lastPossible ->
|
|||
## Returns the given [Str] before the last occurrence of a delimiter, as well as
|
||||
## the rest of the string after that occurrence.
|
||||
## Returns [Err NotFound] if the delimiter is not found.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.splitLast "foo/bar/baz" "/" == Ok { before: "foo/bar", after: "baz" }
|
||||
## expect Str.splitLast "no slashes here" "/" == Err NotFound
|
||||
## ```
|
||||
|
@ -956,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
|
||||
|
@ -964,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
|
||||
|
@ -978,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
|
||||
|
@ -1018,16 +974,16 @@ matchesAtHelp = \state ->
|
|||
## Walks over the `UTF-8` bytes of the given [Str] and calls a function to update
|
||||
## 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
|
||||
## ```roc
|
||||
## 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
|
||||
|
@ -1040,7 +996,7 @@ walkUtf8WithIndexHelp = \string, state, step, index, length ->
|
|||
## Walks over the `UTF-8` bytes of the given [Str] and calls a function to update
|
||||
## state for each byte.
|
||||
##
|
||||
## ```
|
||||
## ```roc
|
||||
## sumOfUtf8Bytes =
|
||||
## Str.walkUtf8 "Hello, World!" 0 \total, byte ->
|
||||
## total + byte
|
||||
|
@ -1051,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
|
||||
|
@ -1081,14 +1037,14 @@ strToNumHelp = \string ->
|
|||
Err InvalidNumStr
|
||||
|
||||
## Adds a prefix to the given [Str].
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.withPrefix "Awesome" "Roc" == "RocAwesome"
|
||||
## ```
|
||||
withPrefix : Str, Str -> Str
|
||||
withPrefix = \str, prefix -> Str.concat prefix str
|
||||
|
||||
## Determines whether or not the first Str contains the second.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.contains "foobarbaz" "bar"
|
||||
## expect !(Str.contains "apple" "orange")
|
||||
## expect Str.contains "anything" ""
|
||||
|
|
|
@ -13,7 +13,7 @@ import Str
|
|||
import Result
|
||||
import Encode exposing [EncoderFormatting, appendWith]
|
||||
import Decode exposing [DecoderFormatting, DecodeResult]
|
||||
import Num exposing [U8, U16, U64, F32, F64, Nat, Dec]
|
||||
import Num exposing [U8, U16, U64, F32, F64, Dec]
|
||||
import Bool exposing [Bool]
|
||||
|
||||
## An opaque type with the `EncoderFormatting` and
|
||||
|
@ -208,14 +208,20 @@ escapedByteToJson = \b ->
|
|||
encodeList = \lst, encodeElem ->
|
||||
Encode.custom \bytes, @Json {} ->
|
||||
writeList = \{ buffer, elemsLeft }, elem ->
|
||||
bufferWithElem = appendWith buffer (encodeElem elem) (@Json {})
|
||||
bufferWithSuffix =
|
||||
if elemsLeft > 1 then
|
||||
List.append bufferWithElem (Num.toU8 ',')
|
||||
else
|
||||
bufferWithElem
|
||||
beforeBufferLen = buffer |> List.len
|
||||
|
||||
{ buffer: bufferWithSuffix, elemsLeft: elemsLeft - 1 }
|
||||
bufferWithElem = appendWith buffer (encodeElem elem) (@Json {})
|
||||
# If our encoder returned [] we just skip the elem
|
||||
if bufferWithElem |> List.len == beforeBufferLen then
|
||||
{ buffer: bufferWithElem, elemsLeft: elemsLeft - 1 }
|
||||
else
|
||||
bufferWithSuffix =
|
||||
if elemsLeft > 1 then
|
||||
List.append bufferWithElem (Num.toU8 ',')
|
||||
else
|
||||
bufferWithElem
|
||||
|
||||
{ buffer: bufferWithSuffix, elemsLeft: elemsLeft - 1 }
|
||||
|
||||
head = List.append bytes (Num.toU8 '[')
|
||||
{ buffer: withList } = List.walk lst { buffer: head, elemsLeft: List.len lst } writeList
|
||||
|
@ -225,21 +231,27 @@ encodeList = \lst, encodeElem ->
|
|||
encodeRecord = \fields ->
|
||||
Encode.custom \bytes, @Json {} ->
|
||||
writeRecord = \{ buffer, fieldsLeft }, { key, value } ->
|
||||
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 {})
|
||||
|
||||
bufferWithSuffix =
|
||||
if fieldsLeft > 1 then
|
||||
List.append bufferWithKeyValue (Num.toU8 ',')
|
||||
else
|
||||
bufferWithKeyValue
|
||||
fieldValue = [] |> appendWith value (json)
|
||||
# If our encoder returned [] we just skip the field
|
||||
if fieldValue == [] then
|
||||
{ buffer, fieldsLeft: fieldsLeft - 1 }
|
||||
else
|
||||
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
|
||||
|> List.concat fieldValue
|
||||
|
||||
{ buffer: bufferWithSuffix, fieldsLeft: fieldsLeft - 1 }
|
||||
bufferWithSuffix =
|
||||
if fieldsLeft > 1 then
|
||||
List.append bufferWithKeyValue (Num.toU8 ',')
|
||||
else
|
||||
bufferWithKeyValue
|
||||
|
||||
{ buffer: bufferWithSuffix, fieldsLeft: fieldsLeft - 1 }
|
||||
|
||||
bytesHead = List.append bytes (Num.toU8 '{')
|
||||
{ buffer: bytesWithRecord } = List.walk fields { buffer: bytesHead, fieldsLeft: List.len fields } writeRecord
|
||||
|
@ -249,16 +261,21 @@ encodeRecord = \fields ->
|
|||
encodeTuple = \elems ->
|
||||
Encode.custom \bytes, @Json {} ->
|
||||
writeTuple = \{ buffer, elemsLeft }, elemEncoder ->
|
||||
bufferWithElem =
|
||||
appendWith buffer elemEncoder (@Json {})
|
||||
beforeBufferLen = buffer |> List.len
|
||||
|
||||
bufferWithSuffix =
|
||||
if elemsLeft > 1 then
|
||||
List.append bufferWithElem (Num.toU8 ',')
|
||||
else
|
||||
bufferWithElem
|
||||
bufferWithElem = appendWith buffer (elemEncoder) (@Json {})
|
||||
|
||||
{ buffer: bufferWithSuffix, elemsLeft: elemsLeft - 1 }
|
||||
# If our encoder returned [] we just skip the elem
|
||||
if bufferWithElem |> List.len == beforeBufferLen then
|
||||
{ buffer: bufferWithElem, elemsLeft: elemsLeft - 1 }
|
||||
else
|
||||
bufferWithSuffix =
|
||||
if elemsLeft > 1 then
|
||||
List.append bufferWithElem (Num.toU8 ',')
|
||||
else
|
||||
bufferWithElem
|
||||
|
||||
{ buffer: bufferWithSuffix, elemsLeft: elemsLeft - 1 }
|
||||
|
||||
bytesHead = List.append bytes (Num.toU8 '[')
|
||||
{ buffer: bytesWithRecord } = List.walk elems { buffer: bytesHead, elemsLeft: List.len elems } writeTuple
|
||||
|
@ -653,21 +670,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
|
||||
|
@ -869,13 +886,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,
|
||||
]
|
||||
|
||||
|
@ -1151,14 +1168,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
|
||||
|
@ -1249,7 +1266,7 @@ decodeRecord = \initialState, stepField, finalizer -> Decode.custom \bytes, @Jso
|
|||
rest = List.dropFirst bytesAfterValue n
|
||||
|
||||
# Build final record from decoded fields and values
|
||||
when finalizer updatedRecord is
|
||||
when finalizer updatedRecord json is
|
||||
Ok val -> { result: Ok val, rest }
|
||||
Err e -> { result: Err e, rest }
|
||||
|
||||
|
@ -1292,13 +1309,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,
|
||||
]
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use roc_module::symbol::Symbol;
|
||||
use roc_target::TargetInfo;
|
||||
use roc_target::Target;
|
||||
use std::ops::Index;
|
||||
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
|
@ -38,7 +38,7 @@ impl FloatWidth {
|
|||
}
|
||||
}
|
||||
|
||||
pub const fn alignment_bytes(&self, target_info: TargetInfo) -> u32 {
|
||||
pub const fn alignment_bytes(&self, target: Target) -> u32 {
|
||||
use roc_target::Architecture::*;
|
||||
use FloatWidth::*;
|
||||
|
||||
|
@ -47,7 +47,7 @@ impl FloatWidth {
|
|||
// the compiler is targeting (e.g. what the Roc code will be compiled to).
|
||||
match self {
|
||||
F32 => 4,
|
||||
F64 => match target_info.architecture {
|
||||
F64 => match target.architecture() {
|
||||
X86_64 | Aarch64 | Wasm32 => 8,
|
||||
X86_32 | Aarch32 => 4,
|
||||
},
|
||||
|
@ -107,7 +107,7 @@ impl IntWidth {
|
|||
}
|
||||
}
|
||||
|
||||
pub const fn alignment_bytes(&self, target_info: TargetInfo) -> u32 {
|
||||
pub const fn alignment_bytes(&self, target: Target) -> u32 {
|
||||
use roc_target::Architecture;
|
||||
use IntWidth::*;
|
||||
|
||||
|
@ -118,7 +118,7 @@ impl IntWidth {
|
|||
U8 | I8 => 1,
|
||||
U16 | I16 => 2,
|
||||
U32 | I32 => 4,
|
||||
U64 | I64 => match target_info.architecture {
|
||||
U64 | I64 => match target.architecture() {
|
||||
Architecture::X86_64
|
||||
| Architecture::Aarch64
|
||||
| Architecture::Aarch32
|
||||
|
@ -130,10 +130,10 @@ impl IntWidth {
|
|||
// according to https://reviews.llvm.org/D28990#655487
|
||||
//
|
||||
// however, rust does not always think that this is true
|
||||
match target_info.architecture {
|
||||
Architecture::X86_64 => 16,
|
||||
Architecture::Aarch64 | Architecture::Aarch32 | Architecture::Wasm32 => 16,
|
||||
Architecture::X86_32 => 8,
|
||||
// Our alignmets here are correct, but they will not match rust/zig/llvm until they update to llvm version 18.
|
||||
match target.architecture() {
|
||||
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");
|
||||
|
@ -330,11 +334,10 @@ pub const NUM_COUNT_LEADING_ZERO_BITS: IntrinsicName =
|
|||
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 NUM_F32_TO_PARTS: &str = "roc_builtins.num.f32_to_parts";
|
||||
pub const NUM_F64_TO_PARTS: &str = "roc_builtins.num.f64_to_parts";
|
||||
pub const NUM_F32_FROM_PARTS: &str = "roc_builtins.num.f32_from_parts";
|
||||
pub const NUM_F64_FROM_PARTS: &str = "roc_builtins.num.f64_from_parts";
|
||||
|
||||
pub const STR_INIT: &str = "roc_builtins.str.init";
|
||||
pub const STR_COUNT_SEGMENTS: &str = "roc_builtins.str.count_segments";
|
||||
|
@ -355,7 +358,7 @@ 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";
|
||||
|
@ -404,6 +407,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";
|
||||
|
@ -415,7 +419,11 @@ pub const DEC_SUB_SATURATED: &str = "roc_builtins.dec.sub_saturated";
|
|||
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_FROM_I128: &str = "roc_builtins.dec.from_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";
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue