mirror of
https://github.com/roc-lang/roc.git
synced 2025-11-25 21:37:48 +00:00
Merge remote-tracking branch 'origin/main' into abilities-syntax
This commit is contained in:
commit
2da41be29f
524 changed files with 47536 additions and 15089 deletions
|
|
@ -35,6 +35,7 @@ fn main() {
|
|||
|
||||
generate_bc_file(&bitcode_path, "ir-i386", "builtins-i386");
|
||||
generate_bc_file(&bitcode_path, "ir-x86_64", "builtins-x86_64");
|
||||
generate_bc_file(&bitcode_path, "ir-aarch64", "builtins-aarch64");
|
||||
generate_bc_file(
|
||||
&bitcode_path,
|
||||
"ir-windows-x86_64",
|
||||
|
|
@ -61,12 +62,12 @@ fn generate_bc_file(bitcode_path: &Path, zig_object: &str, file_name: &str) {
|
|||
ll_path.set_extension("ll");
|
||||
let dest_ir_host = ll_path.to_str().expect("Invalid dest ir path");
|
||||
|
||||
println!("Compiling host ir to: {}", dest_ir_host);
|
||||
println!("Compiling host ir to: {dest_ir_host}");
|
||||
|
||||
let mut bc_path = bitcode_path.join(file_name);
|
||||
bc_path.set_extension("bc");
|
||||
let dest_bc_64bit = bc_path.to_str().expect("Invalid dest bc path");
|
||||
println!("Compiling 64-bit bitcode to: {}", dest_bc_64bit);
|
||||
println!("Compiling 64-bit bitcode to: {dest_bc_64bit}");
|
||||
|
||||
// workaround for github.com/ziglang/zig/issues/9711
|
||||
#[cfg(target_os = "macos")]
|
||||
|
|
@ -104,7 +105,7 @@ fn run_command(mut command: Command, flaky_fail_counter: usize) {
|
|||
false => {
|
||||
let error_str = match str::from_utf8(&output.stderr) {
|
||||
Ok(stderr) => stderr.to_string(),
|
||||
Err(_) => format!("Failed to run \"{}\"", command_str),
|
||||
Err(_) => format!("Failed to run \"{command_str}\""),
|
||||
};
|
||||
|
||||
// Flaky test errors that only occur sometimes on MacOS ci server.
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ fn generate_object_file(bitcode_path: &Path, zig_object: &str, object_file_name:
|
|||
let src_obj_path = bitcode_path.join(object_file_name);
|
||||
let src_obj = src_obj_path.to_str().expect("Invalid src object path");
|
||||
|
||||
println!("Compiling zig object `{}` to: {}", zig_object, src_obj);
|
||||
println!("Compiling zig object `{zig_object}` to: {src_obj}");
|
||||
|
||||
if !DEBUG {
|
||||
let mut zig_cmd = zig();
|
||||
|
|
@ -77,7 +77,7 @@ fn generate_object_file(bitcode_path: &Path, zig_object: &str, object_file_name:
|
|||
|
||||
run_command(zig_cmd, 0);
|
||||
|
||||
println!("Moving zig object `{}` to: {}", zig_object, dest_obj);
|
||||
println!("Moving zig object `{zig_object}` to: {dest_obj}");
|
||||
|
||||
// we store this .o file in rust's `target` folder (for wasm we need to leave a copy here too)
|
||||
fs::copy(src_obj, dest_obj).unwrap_or_else(|err| {
|
||||
|
|
@ -167,7 +167,7 @@ fn run_command(mut command: Command, flaky_fail_counter: usize) {
|
|||
false => {
|
||||
let error_str = match str::from_utf8(&output.stderr) {
|
||||
Ok(stderr) => stderr.to_string(),
|
||||
Err(_) => format!("Failed to run \"{}\"", command_str),
|
||||
Err(_) => format!("Failed to run \"{command_str}\""),
|
||||
};
|
||||
|
||||
// Flaky test errors that only occur sometimes on MacOS ci server.
|
||||
|
|
|
|||
|
|
@ -25,18 +25,19 @@ pub fn build(b: *Builder) void {
|
|||
const host_target = b.standardTargetOptions(.{
|
||||
.default_target = CrossTarget{
|
||||
.cpu_model = .baseline,
|
||||
// TODO allow for native target for maximum speed
|
||||
},
|
||||
});
|
||||
const linux32_target = makeLinux32Target();
|
||||
const linux64_target = makeLinux64Target();
|
||||
const linux_x64_target = makeLinuxX64Target();
|
||||
const linux_aarch64_target = makeLinuxAarch64Target();
|
||||
const windows64_target = makeWindows64Target();
|
||||
const wasm32_target = makeWasm32Target();
|
||||
|
||||
// LLVM IR
|
||||
generateLlvmIrFile(b, mode, host_target, main_path, "ir", "builtins-host");
|
||||
generateLlvmIrFile(b, mode, linux32_target, main_path, "ir-i386", "builtins-i386");
|
||||
generateLlvmIrFile(b, mode, linux64_target, main_path, "ir-x86_64", "builtins-x86_64");
|
||||
generateLlvmIrFile(b, mode, linux_x64_target, main_path, "ir-x86_64", "builtins-x86_64");
|
||||
generateLlvmIrFile(b, mode, linux_aarch64_target, main_path, "ir-aarch64", "builtins-aarch64");
|
||||
generateLlvmIrFile(b, mode, windows64_target, main_path, "ir-windows-x86_64", "builtins-windows-x86_64");
|
||||
generateLlvmIrFile(b, mode, wasm32_target, main_path, "ir-wasm32", "builtins-wasm32");
|
||||
|
||||
|
|
@ -89,6 +90,7 @@ fn generateObjectFile(
|
|||
obj.strip = true;
|
||||
obj.target = target;
|
||||
obj.link_function_sections = true;
|
||||
obj.force_pic = true;
|
||||
const obj_step = b.step(step_name, "Build object file for linking");
|
||||
obj_step.dependOn(&obj.step);
|
||||
}
|
||||
|
|
@ -103,7 +105,17 @@ fn makeLinux32Target() CrossTarget {
|
|||
return target;
|
||||
}
|
||||
|
||||
fn makeLinux64Target() CrossTarget {
|
||||
fn makeLinuxAarch64Target() CrossTarget {
|
||||
var target = CrossTarget.parse(.{}) catch unreachable;
|
||||
|
||||
target.cpu_arch = std.Target.Cpu.Arch.aarch64;
|
||||
target.os_tag = std.Target.Os.Tag.linux;
|
||||
target.abi = std.Target.Abi.musl;
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
fn makeLinuxX64Target() CrossTarget {
|
||||
var target = CrossTarget.parse(.{}) catch unreachable;
|
||||
|
||||
target.cpu_arch = std.Target.Cpu.Arch.x86_64;
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ const v2u64 = @Vector(2, u64);
|
|||
// Export it as weak incase it is already linked in by something else.
|
||||
comptime {
|
||||
@export(__muloti4, .{ .name = "__muloti4", .linkage = .Weak });
|
||||
@export(__lshrti3, .{ .name = "__lshrti3", .linkage = .Weak });
|
||||
if (want_windows_v2u64_abi) {
|
||||
@export(__divti3_windows_x86_64, .{ .name = "__divti3", .linkage = .Weak });
|
||||
@export(__modti3_windows_x86_64, .{ .name = "__modti3", .linkage = .Weak });
|
||||
|
|
@ -440,3 +441,47 @@ pub inline fn floatFractionalBits(comptime T: type) comptime_int {
|
|||
else => @compileError("unknown floating point type " ++ @typeName(T)),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn __lshrti3(a: i128, b: i32) callconv(.C) i128 {
|
||||
return lshrXi3(i128, a, b);
|
||||
}
|
||||
|
||||
// Logical shift right: shift in 0 from left to right
|
||||
// Precondition: 0 <= b < T.bit_count
|
||||
inline fn lshrXi3(comptime T: type, a: T, b: i32) T {
|
||||
const word_t = HalveInt(T, false);
|
||||
const S = std.math.Log2Int(word_t.HalfT);
|
||||
|
||||
const input = word_t{ .all = a };
|
||||
var output: word_t = undefined;
|
||||
|
||||
if (b >= word_t.bits) {
|
||||
output.s.high = 0;
|
||||
output.s.low = input.s.high >> @intCast(S, b - word_t.bits);
|
||||
} else if (b == 0) {
|
||||
return a;
|
||||
} else {
|
||||
output.s.high = input.s.high >> @intCast(S, b);
|
||||
output.s.low = input.s.high << @intCast(S, word_t.bits - b);
|
||||
output.s.low |= input.s.low >> @intCast(S, b);
|
||||
}
|
||||
|
||||
return output.all;
|
||||
}
|
||||
|
||||
/// Allows to access underlying bits as two equally sized lower and higher
|
||||
/// signed or unsigned integers.
|
||||
fn HalveInt(comptime T: type, comptime signed_half: bool) type {
|
||||
return extern union {
|
||||
pub const bits = @divExact(@typeInfo(T).Int.bits, 2);
|
||||
pub const HalfTU = std.meta.Int(.unsigned, bits);
|
||||
pub const HalfTS = std.meta.Int(.signed, bits);
|
||||
pub const HalfT = if (signed_half) HalfTS else HalfTU;
|
||||
|
||||
all: T,
|
||||
s: if (native_endian == .Little)
|
||||
extern struct { low: HalfT, high: HalfT }
|
||||
else
|
||||
extern struct { high: HalfT, low: HalfT },
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -171,8 +171,8 @@ comptime {
|
|||
exportStrFn(str.fromUtf8RangeC, "from_utf8_range");
|
||||
exportStrFn(str.repeat, "repeat");
|
||||
exportStrFn(str.strTrim, "trim");
|
||||
exportStrFn(str.strTrimLeft, "trim_left");
|
||||
exportStrFn(str.strTrimRight, "trim_right");
|
||||
exportStrFn(str.strTrimStart, "trim_start");
|
||||
exportStrFn(str.strTrimEnd, "trim_end");
|
||||
exportStrFn(str.strCloneTo, "clone_to");
|
||||
exportStrFn(str.withCapacity, "with_capacity");
|
||||
exportStrFn(str.strGraphemes, "graphemes");
|
||||
|
|
@ -195,8 +195,10 @@ comptime {
|
|||
exportUtilsFn(utils.test_panic, "test_panic");
|
||||
exportUtilsFn(utils.increfRcPtrC, "incref_rc_ptr");
|
||||
exportUtilsFn(utils.decrefRcPtrC, "decref_rc_ptr");
|
||||
exportUtilsFn(utils.freeRcPtrC, "free_rc_ptr");
|
||||
exportUtilsFn(utils.increfDataPtrC, "incref_data_ptr");
|
||||
exportUtilsFn(utils.decrefDataPtrC, "decref_data_ptr");
|
||||
exportUtilsFn(utils.freeDataPtrC, "free_data_ptr");
|
||||
exportUtilsFn(utils.isUnique, "is_unique");
|
||||
exportUtilsFn(utils.decrefCheckNullC, "decref_check_null");
|
||||
exportUtilsFn(utils.allocateWithRefcountC, "allocate_with_refcount");
|
||||
|
|
|
|||
|
|
@ -2302,7 +2302,7 @@ pub fn strTrim(input_string: RocStr) callconv(.C) RocStr {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn strTrimLeft(input_string: RocStr) callconv(.C) RocStr {
|
||||
pub fn strTrimStart(input_string: RocStr) callconv(.C) RocStr {
|
||||
var string = input_string;
|
||||
|
||||
if (string.isEmpty()) {
|
||||
|
|
@ -2350,7 +2350,7 @@ pub fn strTrimLeft(input_string: RocStr) callconv(.C) RocStr {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn strTrimRight(input_string: RocStr) callconv(.C) RocStr {
|
||||
pub fn strTrimEnd(input_string: RocStr) callconv(.C) RocStr {
|
||||
var string = input_string;
|
||||
|
||||
if (string.isEmpty()) {
|
||||
|
|
@ -2583,22 +2583,22 @@ test "strTrim: small to small" {
|
|||
try expect(trimmed.isSmallStr());
|
||||
}
|
||||
|
||||
test "strTrimLeft: empty" {
|
||||
const trimmedEmpty = strTrimLeft(RocStr.empty());
|
||||
test "strTrimStart: empty" {
|
||||
const trimmedEmpty = strTrimStart(RocStr.empty());
|
||||
try expect(trimmedEmpty.eq(RocStr.empty()));
|
||||
}
|
||||
|
||||
test "strTrimLeft: blank" {
|
||||
test "strTrimStart: blank" {
|
||||
const original_bytes = " ";
|
||||
const original = RocStr.init(original_bytes, original_bytes.len);
|
||||
defer original.decref();
|
||||
|
||||
const trimmed = strTrimLeft(original);
|
||||
const trimmed = strTrimStart(original);
|
||||
|
||||
try expect(trimmed.eq(RocStr.empty()));
|
||||
}
|
||||
|
||||
test "strTrimLeft: large to large" {
|
||||
test "strTrimStart: large to large" {
|
||||
const original_bytes = " hello even more giant world ";
|
||||
const original = RocStr.init(original_bytes, original_bytes.len);
|
||||
defer original.decref();
|
||||
|
|
@ -2611,12 +2611,12 @@ test "strTrimLeft: large to large" {
|
|||
|
||||
try expect(!expected.isSmallStr());
|
||||
|
||||
const trimmed = strTrimLeft(original);
|
||||
const trimmed = strTrimStart(original);
|
||||
|
||||
try expect(trimmed.eq(expected));
|
||||
}
|
||||
|
||||
test "strTrimLeft: large to small" {
|
||||
test "strTrimStart: large to small" {
|
||||
// `original` will be consumed by the concat; do not free explicitly
|
||||
const original_bytes = " hello ";
|
||||
const original = RocStr.init(original_bytes, original_bytes.len);
|
||||
|
|
@ -2629,14 +2629,14 @@ test "strTrimLeft: large to small" {
|
|||
|
||||
try expect(expected.isSmallStr());
|
||||
|
||||
const trimmed = strTrimLeft(original);
|
||||
const trimmed = strTrimStart(original);
|
||||
defer trimmed.decref();
|
||||
|
||||
try expect(trimmed.eq(expected));
|
||||
try expect(!trimmed.isSmallStr());
|
||||
}
|
||||
|
||||
test "strTrimLeft: small to small" {
|
||||
test "strTrimStart: small to small" {
|
||||
const original_bytes = " hello ";
|
||||
const original = RocStr.init(original_bytes, original_bytes.len);
|
||||
defer original.decref();
|
||||
|
|
@ -2649,28 +2649,28 @@ test "strTrimLeft: small to small" {
|
|||
|
||||
try expect(expected.isSmallStr());
|
||||
|
||||
const trimmed = strTrimLeft(original);
|
||||
const trimmed = strTrimStart(original);
|
||||
|
||||
try expect(trimmed.eq(expected));
|
||||
try expect(trimmed.isSmallStr());
|
||||
}
|
||||
|
||||
test "strTrimRight: empty" {
|
||||
const trimmedEmpty = strTrimRight(RocStr.empty());
|
||||
test "strTrimEnd: empty" {
|
||||
const trimmedEmpty = strTrimEnd(RocStr.empty());
|
||||
try expect(trimmedEmpty.eq(RocStr.empty()));
|
||||
}
|
||||
|
||||
test "strTrimRight: blank" {
|
||||
test "strTrimEnd: blank" {
|
||||
const original_bytes = " ";
|
||||
const original = RocStr.init(original_bytes, original_bytes.len);
|
||||
defer original.decref();
|
||||
|
||||
const trimmed = strTrimRight(original);
|
||||
const trimmed = strTrimEnd(original);
|
||||
|
||||
try expect(trimmed.eq(RocStr.empty()));
|
||||
}
|
||||
|
||||
test "strTrimRight: large to large" {
|
||||
test "strTrimEnd: large to large" {
|
||||
const original_bytes = " hello even more giant world ";
|
||||
const original = RocStr.init(original_bytes, original_bytes.len);
|
||||
defer original.decref();
|
||||
|
|
@ -2683,12 +2683,12 @@ test "strTrimRight: large to large" {
|
|||
|
||||
try expect(!expected.isSmallStr());
|
||||
|
||||
const trimmed = strTrimRight(original);
|
||||
const trimmed = strTrimEnd(original);
|
||||
|
||||
try expect(trimmed.eq(expected));
|
||||
}
|
||||
|
||||
test "strTrimRight: large to small" {
|
||||
test "strTrimEnd: large to small" {
|
||||
// `original` will be consumed by the concat; do not free explicitly
|
||||
const original_bytes = " hello ";
|
||||
const original = RocStr.init(original_bytes, original_bytes.len);
|
||||
|
|
@ -2701,14 +2701,14 @@ test "strTrimRight: large to small" {
|
|||
|
||||
try expect(expected.isSmallStr());
|
||||
|
||||
const trimmed = strTrimRight(original);
|
||||
const trimmed = strTrimEnd(original);
|
||||
defer trimmed.decref();
|
||||
|
||||
try expect(trimmed.eq(expected));
|
||||
try expect(!trimmed.isSmallStr());
|
||||
}
|
||||
|
||||
test "strTrimRight: small to small" {
|
||||
test "strTrimEnd: small to small" {
|
||||
const original_bytes = " hello ";
|
||||
const original = RocStr.init(original_bytes, original_bytes.len);
|
||||
defer original.decref();
|
||||
|
|
@ -2721,7 +2721,7 @@ test "strTrimRight: small to small" {
|
|||
|
||||
try expect(expected.isSmallStr());
|
||||
|
||||
const trimmed = strTrimRight(original);
|
||||
const trimmed = strTrimEnd(original);
|
||||
|
||||
try expect(trimmed.eq(expected));
|
||||
try expect(trimmed.isSmallStr());
|
||||
|
|
|
|||
|
|
@ -220,6 +220,29 @@ pub fn increfDataPtrC(
|
|||
return increfRcPtrC(isizes, inc_amount);
|
||||
}
|
||||
|
||||
pub fn freeDataPtrC(
|
||||
bytes_or_null: ?[*]isize,
|
||||
alignment: u32,
|
||||
) callconv(.C) void {
|
||||
var bytes = bytes_or_null orelse return;
|
||||
|
||||
const ptr = @ptrToInt(bytes);
|
||||
const tag_mask: usize = if (@sizeOf(usize) == 8) 0b111 else 0b11;
|
||||
const masked_ptr = ptr & ~tag_mask;
|
||||
|
||||
const isizes: [*]isize = @intToPtr([*]isize, masked_ptr);
|
||||
|
||||
return freeRcPtrC(isizes - 1, alignment);
|
||||
}
|
||||
|
||||
pub fn freeRcPtrC(
|
||||
bytes_or_null: ?[*]isize,
|
||||
alignment: u32,
|
||||
) callconv(.C) void {
|
||||
var bytes = bytes_or_null orelse return;
|
||||
return free_ptr_to_refcount(bytes, alignment);
|
||||
}
|
||||
|
||||
pub fn decref(
|
||||
bytes_or_null: ?[*]u8,
|
||||
data_bytes: usize,
|
||||
|
|
@ -236,13 +259,23 @@ pub fn decref(
|
|||
decref_ptr_to_refcount(isizes - 1, alignment);
|
||||
}
|
||||
|
||||
inline fn decref_ptr_to_refcount(
|
||||
inline fn free_ptr_to_refcount(
|
||||
refcount_ptr: [*]isize,
|
||||
alignment: u32,
|
||||
) void {
|
||||
if (RC_TYPE == Refcount.none) return;
|
||||
const extra_bytes = std.math.max(alignment, @sizeOf(usize));
|
||||
|
||||
// NOTE: we don't even check whether the refcount is "infinity" here!
|
||||
dealloc(@ptrCast([*]u8, refcount_ptr) - (extra_bytes - @sizeOf(usize)), alignment);
|
||||
}
|
||||
|
||||
inline fn decref_ptr_to_refcount(
|
||||
refcount_ptr: [*]isize,
|
||||
alignment: u32,
|
||||
) void {
|
||||
if (RC_TYPE == Refcount.none) return;
|
||||
|
||||
if (DEBUG_INCDEC and builtin.target.cpu.arch != .wasm32) {
|
||||
std.debug.print("| decrement {*}: ", .{refcount_ptr});
|
||||
}
|
||||
|
|
@ -264,13 +297,13 @@ inline fn decref_ptr_to_refcount(
|
|||
}
|
||||
|
||||
if (refcount == REFCOUNT_ONE_ISIZE) {
|
||||
dealloc(@ptrCast([*]u8, refcount_ptr) - (extra_bytes - @sizeOf(usize)), alignment);
|
||||
free_ptr_to_refcount(refcount_ptr, alignment);
|
||||
}
|
||||
},
|
||||
Refcount.atomic => {
|
||||
var last = @atomicRmw(isize, &refcount_ptr[0], std.builtin.AtomicRmwOp.Sub, 1, Monotonic);
|
||||
if (last == REFCOUNT_ONE_ISIZE) {
|
||||
dealloc(@ptrCast([*]u8, refcount_ptr) - (extra_bytes - @sizeOf(usize)), alignment);
|
||||
free_ptr_to_refcount(refcount_ptr, alignment);
|
||||
}
|
||||
},
|
||||
Refcount.none => unreachable,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
## Most users won't need Box, it is used for:
|
||||
## - Holding unknown Roc types when developing [platforms](https://github.com/roc-lang/roc/wiki/Roc-concepts-explained#platform).
|
||||
## - To improve performance in rare cases.
|
||||
##
|
||||
interface Box
|
||||
exposes [box, unbox]
|
||||
imports []
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ interface Dict
|
|||
clear,
|
||||
capacity,
|
||||
len,
|
||||
isEmpty,
|
||||
get,
|
||||
contains,
|
||||
insert,
|
||||
|
|
@ -21,6 +22,8 @@ interface Dict
|
|||
insertAll,
|
||||
keepShared,
|
||||
removeAll,
|
||||
map,
|
||||
joinMap,
|
||||
]
|
||||
imports [
|
||||
Bool.{ Bool, Eq },
|
||||
|
|
@ -98,14 +101,14 @@ Dict k v := {
|
|||
data : List (k, v),
|
||||
size : Nat,
|
||||
} where k implements Hash & Eq
|
||||
implements [
|
||||
Eq {
|
||||
isEq,
|
||||
},
|
||||
Hash {
|
||||
hash: hashDict,
|
||||
},
|
||||
]
|
||||
implements [
|
||||
Eq {
|
||||
isEq,
|
||||
},
|
||||
Hash {
|
||||
hash: hashDict,
|
||||
},
|
||||
]
|
||||
|
||||
isEq : Dict k v, Dict k v -> Bool where k implements Hash & Eq, v implements Eq
|
||||
isEq = \xs, ys ->
|
||||
|
|
@ -139,12 +142,12 @@ empty = \{} ->
|
|||
## Returns the max number of elements the dictionary can hold before requiring a rehash.
|
||||
## ```
|
||||
## foodDict =
|
||||
## Dict.empty {}
|
||||
## |> Dict.insert "apple" "fruit"
|
||||
## Dict.empty {}
|
||||
## |> Dict.insert "apple" "fruit"
|
||||
##
|
||||
## capacityOfDict = Dict.capacity foodDict
|
||||
## ```
|
||||
capacity : Dict k v -> Nat where k implements Hash & Eq
|
||||
capacity : Dict * * -> Nat
|
||||
capacity = \@Dict { dataIndices } ->
|
||||
cap = List.len dataIndices
|
||||
|
||||
|
|
@ -192,10 +195,20 @@ fromList = \data ->
|
|||
## |> Dict.len
|
||||
## |> Bool.isEq 3
|
||||
## ```
|
||||
len : Dict k v -> Nat where k implements Hash & Eq
|
||||
len : Dict * * -> Nat
|
||||
len = \@Dict { size } ->
|
||||
size
|
||||
|
||||
## Check if the dictinoary is empty.
|
||||
## ```
|
||||
## Dict.isEmpty (Dict.empty {} |> Dict.insert "key" 42)
|
||||
##
|
||||
## Dict.isEmpty (Dict.empty {})
|
||||
## ```
|
||||
isEmpty : Dict * * -> Bool
|
||||
isEmpty = \@Dict { size } ->
|
||||
size == 0
|
||||
|
||||
## Clears all elements from a dictionary keeping around the allocation if it isn't huge.
|
||||
## ```
|
||||
## songs =
|
||||
|
|
@ -225,6 +238,30 @@ clear = \@Dict { metadata, dataIndices, data } ->
|
|||
size: 0,
|
||||
}
|
||||
|
||||
## Convert each value in the dictionary to something new, by calling a conversion
|
||||
## function on each of them which receives both the key and the old value. Then return a
|
||||
## new dictionary containing the same keys and the converted values.
|
||||
map : Dict k a, (k, a -> b) -> Dict k b
|
||||
where k implements Hash & Eq, b implements Hash & Eq
|
||||
map = \dict, transform ->
|
||||
init = withCapacity (capacity dict)
|
||||
|
||||
walk dict init \answer, k, v ->
|
||||
insert answer k (transform k v)
|
||||
|
||||
## Like [Dict.map], except the transformation function wraps the return value
|
||||
## in a dictionary. At the end, all the dictionaries get joined together
|
||||
## (using [Dict.insertAll]) into one dictionary.
|
||||
##
|
||||
## You may know a similar function named `concatMap` in other languages.
|
||||
joinMap : Dict a b, (a, b -> Dict x y) -> Dict x y
|
||||
where a implements Hash & Eq, x implements Hash & Eq
|
||||
joinMap = \dict, transform ->
|
||||
init = withCapacity (capacity dict) # Might be a pessimization
|
||||
|
||||
walk dict init \answer, k, v ->
|
||||
insertAll answer (transform k v)
|
||||
|
||||
## 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.
|
||||
|
|
@ -976,16 +1013,16 @@ expect
|
|||
# TODO: Add a builtin to distinguish big endian systems and change loading orders.
|
||||
# TODO: Switch out Wymum on systems with slow 128bit multiplication.
|
||||
LowLevelHasher := { originalSeed : U64, state : U64 } implements [
|
||||
Hasher {
|
||||
addBytes,
|
||||
addU8,
|
||||
addU16,
|
||||
addU32,
|
||||
addU64,
|
||||
addU128,
|
||||
complete,
|
||||
},
|
||||
]
|
||||
Hasher {
|
||||
addBytes,
|
||||
addU8,
|
||||
addU16,
|
||||
addU32,
|
||||
addU64,
|
||||
addU128,
|
||||
complete,
|
||||
},
|
||||
]
|
||||
|
||||
# unsafe primitive that does not perform a bounds check
|
||||
# TODO hide behind an InternalList.roc module
|
||||
|
|
|
|||
|
|
@ -208,6 +208,9 @@ interface List
|
|||
## * Even when copying is faster, other list operations may still be slightly slower with persistent data structures. For example, even if it were a persistent data structure, [List.map], [List.walk], and [List.keepIf] would all need to traverse every element in the list and build up the result from scratch. These operations are all
|
||||
## * Roc's compiler optimizes many list operations into in-place mutations behind the scenes, depending on how the list is being used. For example, [List.map], [List.keepIf], and [List.set] can all be optimized to perform in-place mutations.
|
||||
## * If possible, it is usually best for performance to use large lists in a way where the optimizer can turn them into in-place mutations. If this is not possible, a persistent data structure might be faster - but this is a rare enough scenario that it would not be good for the average Roc program's performance if this were the way [List] worked by default. Instead, you can look outside Roc's standard modules for an implementation of a persistent data structure - likely built using [List] under the hood!
|
||||
|
||||
# separator so List.isEmpty doesn't absorb the above into its doc comment
|
||||
|
||||
## Check if the list is empty.
|
||||
## ```
|
||||
## List.isEmpty [1, 2, 3]
|
||||
|
|
@ -222,6 +225,13 @@ isEmpty = \list ->
|
|||
# but will cause a reference count increment on the value it got out of the list
|
||||
getUnsafe : List a, Nat -> a
|
||||
|
||||
## Returns an element from a list at the given index.
|
||||
##
|
||||
## Returns `Err OutOfBounds` if the given index exceeds the List's length
|
||||
## ```
|
||||
## 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, index ->
|
||||
if index < List.len list then
|
||||
|
|
@ -352,6 +362,10 @@ 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.
|
||||
## ```
|
||||
## expect List.last [1, 2, 3] == Ok 3
|
||||
## expect List.last [] == Err ListWasEmpty
|
||||
## ```
|
||||
last : List a -> Result a [ListWasEmpty]
|
||||
last = \list ->
|
||||
when List.get list (Num.subSaturated (List.len list) 1) is
|
||||
|
|
@ -383,7 +397,7 @@ repeatHelp = \value, count, accum ->
|
|||
|
||||
## Returns the list with its elements reversed.
|
||||
## ```
|
||||
## List.reverse [1, 2, 3]
|
||||
## expect List.reverse [1, 2, 3] == [3, 2, 1]
|
||||
## ```
|
||||
reverse : List a -> List a
|
||||
reverse = \list ->
|
||||
|
|
@ -397,9 +411,9 @@ reverseHelp = \list, left, right ->
|
|||
|
||||
## Join the given lists together into one list.
|
||||
## ```
|
||||
## List.join [[1, 2, 3], [4, 5], [], [6, 7]]
|
||||
## List.join [[], []]
|
||||
## List.join []
|
||||
## expect List.join [[1], [2, 3], [], [4, 5]] == [1, 2, 3, 4, 5]
|
||||
## expect List.join [[], []] == []
|
||||
## expect List.join [] == []
|
||||
## ```
|
||||
join : List (List a) -> List a
|
||||
join = \lists ->
|
||||
|
|
@ -597,6 +611,10 @@ 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`.
|
||||
## ```
|
||||
## 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, predicate ->
|
||||
walkState = \state, elem ->
|
||||
|
|
@ -610,11 +628,12 @@ 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.
|
||||
## ```
|
||||
## List.keepOks [["a", "b"], [], [], ["c", "d", "e"]] List.last
|
||||
## expect List.keepOks ["1", "Two", "23", "Bird"] Str.toI32 == [1, 23]
|
||||
##
|
||||
## fn = \str -> if Str.isEmpty str then Err StrWasEmpty else Ok (Str.len str)
|
||||
## expect List.keepOks [["a", "b"], [], ["c", "d", "e"], [] ] List.first == ["a", "c"]
|
||||
##
|
||||
## List.keepOks ["", "a", "bc", "", "d", "ef", ""]
|
||||
## fn = \str -> if Str.isEmpty str then Err StrWasEmpty else Ok str
|
||||
## expect List.keepOks ["", "a", "bc", "", "d", "ef", ""] fn == ["a", "bc", "d", "ef"]
|
||||
## ```
|
||||
keepOks : List before, (before -> Result after *) -> List after
|
||||
keepOks = \list, toResult ->
|
||||
|
|
@ -646,9 +665,9 @@ 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.
|
||||
## ```
|
||||
## List.map [1, 2, 3] (\num -> num + 1)
|
||||
## expect List.map [1, 2, 3] (\num -> num + 1) == [2, 3, 4]
|
||||
##
|
||||
## List.map ["", "a", "bc"] Str.isEmpty
|
||||
## expect List.map ["", "a", "bc"] Str.isEmpty == [Bool.true, Bool.false, Bool.false]
|
||||
## ```
|
||||
map : List a, (a -> b) -> List b
|
||||
|
||||
|
|
@ -675,6 +694,9 @@ 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.
|
||||
## ```
|
||||
## expect List.mapWithIndex [10, 20, 30] (\num, index -> num + index) == [10, 21, 32]
|
||||
## ```
|
||||
mapWithIndex : List a, (a, Nat -> b) -> List b
|
||||
mapWithIndex = \src, func ->
|
||||
length = len src
|
||||
|
|
@ -1160,8 +1182,17 @@ mapTry = \list, toResult ->
|
|||
Result.map (toResult elem) \ok ->
|
||||
List.append state ok
|
||||
|
||||
## This is the same as `iterate` but with [Result] instead of `[Continue, Break]`.
|
||||
## Using `Result` saves a conditional in `mapTry`.
|
||||
## Same as [List.walk], except you can stop walking early by returning `Err`.
|
||||
##
|
||||
## ## Performance Details
|
||||
##
|
||||
## Compared to [List.walk], this can potentially visit fewer elements (which can
|
||||
## improve performance) at the cost of making each step take longer.
|
||||
## However, the added cost to each step is extremely small, and can easily
|
||||
## be outweighed if it results in skipping even a small number of elements.
|
||||
##
|
||||
## As such, it is typically better for performance to use this over [List.walk]
|
||||
## if returning `Break` earlier than the last element is expected to be common.
|
||||
walkTry : List elem, state, (state, elem -> Result state err) -> Result state err
|
||||
walkTry = \list, init, func ->
|
||||
walkTryHelp list init func 0 (List.len list)
|
||||
|
|
|
|||
|
|
@ -33,12 +33,17 @@ interface Num
|
|||
Decimal,
|
||||
Binary32,
|
||||
Binary64,
|
||||
e,
|
||||
pi,
|
||||
tau,
|
||||
abs,
|
||||
absDiff,
|
||||
neg,
|
||||
add,
|
||||
sub,
|
||||
mul,
|
||||
min,
|
||||
max,
|
||||
isLt,
|
||||
isLte,
|
||||
isGt,
|
||||
|
|
@ -81,6 +86,7 @@ interface Num
|
|||
bitwiseAnd,
|
||||
bitwiseXor,
|
||||
bitwiseOr,
|
||||
bitwiseNot,
|
||||
shiftLeftBy,
|
||||
shiftRightBy,
|
||||
shiftRightZfBy,
|
||||
|
|
@ -492,6 +498,18 @@ F32 : Num (FloatingPoint Binary32)
|
|||
## [sqrt] and trigonometry are massively slower with [Dec] than with [F64].
|
||||
Dec : Num (FloatingPoint Decimal)
|
||||
|
||||
## Euler's number (e)
|
||||
e : Frac *
|
||||
e = 2.71828182845904523536028747135266249775724709369995
|
||||
|
||||
## Archimedes' constant (π)
|
||||
pi : Frac *
|
||||
pi = 3.14159265358979323846264338327950288419716939937510
|
||||
|
||||
## Circle constant (τ)
|
||||
tau : Frac *
|
||||
tau = 2 * pi
|
||||
|
||||
# ------- Functions
|
||||
## Convert a number to a [Str].
|
||||
##
|
||||
|
|
@ -777,6 +795,34 @@ sub : Num a, Num a -> Num a
|
|||
## ∞ or -∞. For all other number types, overflow results in a panic.
|
||||
mul : Num a, Num a -> Num a
|
||||
|
||||
## Obtains the smaller between two numbers of the same type.
|
||||
##
|
||||
## ```
|
||||
## Num.min 100 0
|
||||
##
|
||||
## Num.min 3.0 -3.0
|
||||
## ```
|
||||
min : Num a, Num a -> Num a
|
||||
min = \a, b ->
|
||||
if a < b then
|
||||
a
|
||||
else
|
||||
b
|
||||
|
||||
## Obtains the greater between two numbers of the same type.
|
||||
##
|
||||
## ```
|
||||
## Num.max 100 0
|
||||
##
|
||||
## Num.max 3.0 -3.0
|
||||
## ```
|
||||
max : Num a, Num a -> Num a
|
||||
max = \a, b ->
|
||||
if a > b then
|
||||
a
|
||||
else
|
||||
b
|
||||
|
||||
sin : Frac a -> Frac a
|
||||
cos : Frac a -> Frac a
|
||||
|
||||
|
|
@ -930,10 +976,25 @@ remChecked = \a, b ->
|
|||
|
||||
isMultipleOf : Int a, Int a -> Bool
|
||||
|
||||
## Does a "bitwise and". Each bit of the output is 1 if the corresponding bit
|
||||
## of x AND of y is 1, otherwise it's 0.
|
||||
bitwiseAnd : Int a, Int a -> Int a
|
||||
|
||||
## Does a "bitwise exclusive or". Each bit of the output is the same as the
|
||||
## corresponding bit in x if that bit in y is 0, and it's the complement of
|
||||
## the bit in x if that bit in y is 1.
|
||||
bitwiseXor : Int a, Int a -> Int a
|
||||
|
||||
## Does a "bitwise or". Each bit of the output is 0 if the corresponding bit
|
||||
## of x OR of y is 0, otherwise it's 1.
|
||||
bitwiseOr : Int a, Int a -> Int a
|
||||
|
||||
## Returns the complement of x - the number you get by switching each 1 for a
|
||||
## 0 and each 0 for a 1. This is the same as -x - 1.
|
||||
bitwiseNot : Int a -> Int a
|
||||
bitwiseNot = \n ->
|
||||
bitwiseXor n (subWrap 0 1)
|
||||
|
||||
## Bitwise left shift of a number by another
|
||||
##
|
||||
## The least significant bits always become 0. This means that shifting left is
|
||||
|
|
|
|||
|
|
@ -42,8 +42,8 @@ withDefault = \result, default ->
|
|||
## 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`.
|
||||
## ```
|
||||
## Result.map (Ok 12) Num.negate
|
||||
## Result.map (Err "yipes!") Num.negate
|
||||
## Result.map (Ok 12) Num.neg
|
||||
## Result.map (Err "yipes!") Num.neg
|
||||
## ```
|
||||
##
|
||||
## Functions like `map` are common in Roc; see for example [List.map],
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ interface Set
|
|||
walkUntil,
|
||||
insert,
|
||||
len,
|
||||
isEmpty,
|
||||
capacity,
|
||||
remove,
|
||||
contains,
|
||||
toList,
|
||||
|
|
@ -14,6 +16,8 @@ interface Set
|
|||
union,
|
||||
intersection,
|
||||
difference,
|
||||
map,
|
||||
joinMap,
|
||||
]
|
||||
imports [
|
||||
List,
|
||||
|
|
@ -26,14 +30,14 @@ interface Set
|
|||
## Provides a [set](https://en.wikipedia.org/wiki/Set_(abstract_data_type))
|
||||
## type which stores a collection of unique values, without any ordering
|
||||
Set k := Dict.Dict k {} where k implements Hash & Eq
|
||||
implements [
|
||||
Eq {
|
||||
isEq,
|
||||
},
|
||||
Hash {
|
||||
hash: hashSet,
|
||||
},
|
||||
]
|
||||
implements [
|
||||
Eq {
|
||||
isEq,
|
||||
},
|
||||
Hash {
|
||||
hash: hashSet,
|
||||
},
|
||||
]
|
||||
|
||||
isEq : Set k, Set k -> Bool where k implements Hash & Eq
|
||||
isEq = \xs, ys ->
|
||||
|
|
@ -59,6 +63,13 @@ hashSet = \hasher, @Set inner -> Hash.hash hasher inner
|
|||
empty : {} -> Set k where k implements Hash & Eq
|
||||
empty = \{} -> @Set (Dict.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 -> Set k where k implements Hash & Eq
|
||||
withCapacity = \cap ->
|
||||
@Set (Dict.withCapacity cap)
|
||||
|
||||
## Creates a new `Set` with a single value.
|
||||
## ```
|
||||
## singleItemSet = Set.single "Apple"
|
||||
|
|
@ -115,10 +126,32 @@ expect
|
|||
##
|
||||
## expect countValues == 3
|
||||
## ```
|
||||
len : Set k -> Nat where k implements Hash & Eq
|
||||
len : Set * -> Nat
|
||||
len = \@Set dict ->
|
||||
Dict.len dict
|
||||
|
||||
## Returns the max number of elements the set can hold before requiring a rehash.
|
||||
## ```
|
||||
## foodSet =
|
||||
## Set.empty {}
|
||||
## |> Set.insert "apple"
|
||||
##
|
||||
## capacityOfSet = Set.capacity foodSet
|
||||
## ```
|
||||
capacity : Set * -> Nat
|
||||
capacity = \@Set dict ->
|
||||
Dict.capacity dict
|
||||
|
||||
## Check if the set is empty.
|
||||
## ```
|
||||
## Set.isEmpty (Set.empty {} |> Set.insert 42)
|
||||
##
|
||||
## Set.isEmpty (Set.empty {})
|
||||
## ```
|
||||
isEmpty : Set * -> Bool
|
||||
isEmpty = \@Set dict ->
|
||||
Dict.isEmpty dict
|
||||
|
||||
# Inserting a duplicate key has no effect on length.
|
||||
expect
|
||||
actual =
|
||||
|
|
@ -261,6 +294,28 @@ walk : Set k, state, (state, k -> state) -> state where k implements Hash & Eq
|
|||
walk = \@Set dict, state, step ->
|
||||
Dict.walk dict state (\s, k, _ -> step s k)
|
||||
|
||||
## Convert each value in the set to something new, by calling a conversion
|
||||
## function on each of them which receives the old value. Then return a
|
||||
## new set containing the converted values.
|
||||
map : Set a, (a -> b) -> Set b where a implements Hash & Eq, b implements Hash & Eq
|
||||
map = \set, transform ->
|
||||
init = withCapacity (capacity set)
|
||||
|
||||
walk set init \answer, k ->
|
||||
insert answer (transform k)
|
||||
|
||||
## Like [Set.map], except the transformation function wraps the return value
|
||||
## in a set. At the end, all the sets get joined together
|
||||
## (using [Set.union]) into one set.
|
||||
##
|
||||
## You may know a similar function named `concatMap` in other languages.
|
||||
joinMap : Set a, (a -> Set b) -> Set b where a implements Hash & Eq, b implements Hash & Eq
|
||||
joinMap = \set, transform ->
|
||||
init = withCapacity (capacity set) # Might be a pessimization
|
||||
|
||||
walk set init \answer, k ->
|
||||
union answer (transform k)
|
||||
|
||||
## Iterate through the values of a given `Set` and build a value, can stop
|
||||
## iterating part way through the collection.
|
||||
## ```
|
||||
|
|
|
|||
|
|
@ -103,8 +103,8 @@ interface Str
|
|||
startsWith,
|
||||
endsWith,
|
||||
trim,
|
||||
trimLeft,
|
||||
trimRight,
|
||||
trimStart,
|
||||
trimEnd,
|
||||
toDec,
|
||||
toF64,
|
||||
toF32,
|
||||
|
|
@ -448,15 +448,15 @@ trim : Str -> Str
|
|||
|
||||
## Return the [Str] with all whitespace removed from the beginning.
|
||||
## ```
|
||||
## expect Str.trimLeft " Hello \n\n" == "Hello \n\n"
|
||||
## expect Str.trimStart " Hello \n\n" == "Hello \n\n"
|
||||
## ```
|
||||
trimLeft : Str -> Str
|
||||
trimStart : Str -> Str
|
||||
|
||||
## Return the [Str] with all whitespace removed from the end.
|
||||
## ```
|
||||
## expect Str.trimRight " Hello \n\n" == " Hello"
|
||||
## expect Str.trimEnd " Hello \n\n" == " Hello"
|
||||
## ```
|
||||
trimRight : Str -> Str
|
||||
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).
|
||||
|
|
@ -638,12 +638,13 @@ countUtf8Bytes : Str -> Nat
|
|||
substringUnsafe : Str, Nat, Nat -> Str
|
||||
|
||||
## Returns the given [Str] with each occurrence of a substring replaced.
|
||||
## Returns [Err NotFound] if the substring is not found.
|
||||
## If the substring is not found, returns the original string.
|
||||
##
|
||||
## ```
|
||||
## expect Str.replaceEach "foo/bar/baz" "/" "_" == Ok "foo_bar_baz"
|
||||
## expect Str.replaceEach "not here" "/" "_" == Err NotFound
|
||||
## expect Str.replaceEach "foo/bar/baz" "/" "_" == "foo_bar_baz"
|
||||
## expect Str.replaceEach "not here" "/" "_" == "not here"
|
||||
## ```
|
||||
replaceEach : Str, Str, Str -> Result Str [NotFound]
|
||||
replaceEach : Str, Str, Str -> Str
|
||||
replaceEach = \haystack, needle, flower ->
|
||||
when splitFirst haystack needle is
|
||||
Ok { before, after } ->
|
||||
|
|
@ -653,9 +654,8 @@ replaceEach = \haystack, needle, flower ->
|
|||
|> Str.concat before
|
||||
|> Str.concat flower
|
||||
|> replaceEachHelp after needle flower
|
||||
|> Ok
|
||||
|
||||
Err err -> Err err
|
||||
Err NotFound -> haystack
|
||||
|
||||
replaceEachHelp : Str, Str, Str, Str -> Str
|
||||
replaceEachHelp = \buf, haystack, needle, flower ->
|
||||
|
|
@ -668,39 +668,44 @@ replaceEachHelp = \buf, haystack, needle, flower ->
|
|||
|
||||
Err NotFound -> Str.concat buf haystack
|
||||
|
||||
expect Str.replaceEach "abXdeXghi" "X" "_" == Ok "ab_de_ghi"
|
||||
expect Str.replaceEach "abXdeXghi" "X" "_" == "ab_de_ghi"
|
||||
expect Str.replaceEach "abcdefg" "nothing" "_" == "abcdefg"
|
||||
|
||||
## Returns the given [Str] with the first occurrence of a substring replaced.
|
||||
## Returns [Err NotFound] if the substring is not found.
|
||||
## If the substring is not found, returns the original string.
|
||||
##
|
||||
## ```
|
||||
## expect Str.replaceFirst "foo/bar/baz" "/" "_" == Ok "foo_bar/baz"
|
||||
## expect Str.replaceFirst "no slashes here" "/" "_" == Err NotFound
|
||||
## expect Str.replaceFirst "foo/bar/baz" "/" "_" == "foo_bar/baz"
|
||||
## expect Str.replaceFirst "no slashes here" "/" "_" == "no slashes here"
|
||||
## ```
|
||||
replaceFirst : Str, Str, Str -> Result Str [NotFound]
|
||||
replaceFirst : Str, Str, Str -> Str
|
||||
replaceFirst = \haystack, needle, flower ->
|
||||
when splitFirst haystack needle is
|
||||
Ok { before, after } ->
|
||||
Ok "\(before)\(flower)\(after)"
|
||||
"\(before)\(flower)\(after)"
|
||||
|
||||
Err err -> Err err
|
||||
Err NotFound -> haystack
|
||||
|
||||
expect Str.replaceFirst "abXdeXghi" "X" "_" == Ok "ab_deXghi"
|
||||
expect Str.replaceFirst "abXdeXghi" "X" "_" == "ab_deXghi"
|
||||
expect Str.replaceFirst "abcdefg" "nothing" "_" == "abcdefg"
|
||||
|
||||
## Returns the given [Str] with the last occurrence of a substring replaced.
|
||||
## Returns [Err NotFound] if the substring is not found.
|
||||
## If the substring is not found, returns the original string.
|
||||
##
|
||||
## ```
|
||||
## expect Str.replaceLast "foo/bar/baz" "/" "_" == Ok "foo/bar_baz"
|
||||
## expect Str.replaceLast "no slashes here" "/" "_" == Err NotFound
|
||||
## expect Str.replaceLast "foo/bar/baz" "/" "_" == "foo/bar_baz"
|
||||
## expect Str.replaceLast "no slashes here" "/" "_" == "no slashes here"
|
||||
## ```
|
||||
replaceLast : Str, Str, Str -> Result Str [NotFound]
|
||||
replaceLast : Str, Str, Str -> Str
|
||||
replaceLast = \haystack, needle, flower ->
|
||||
when splitLast haystack needle is
|
||||
Ok { before, after } ->
|
||||
Ok "\(before)\(flower)\(after)"
|
||||
"\(before)\(flower)\(after)"
|
||||
|
||||
Err err -> Err err
|
||||
Err NotFound -> haystack
|
||||
|
||||
expect Str.replaceLast "abXdeXghi" "X" "_" == Ok "abXde_ghi"
|
||||
expect Str.replaceLast "abXdeXghi" "X" "_" == "abXde_ghi"
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -44,49 +44,49 @@ interface TotallyNotJson
|
|||
## An opaque type with the `EncoderFormatting` and
|
||||
## `DecoderFormatting` abilities.
|
||||
Json := { fieldNameMapping : FieldNameMapping }
|
||||
implements [
|
||||
EncoderFormatting {
|
||||
u8: encodeU8,
|
||||
u16: encodeU16,
|
||||
u32: encodeU32,
|
||||
u64: encodeU64,
|
||||
u128: encodeU128,
|
||||
i8: encodeI8,
|
||||
i16: encodeI16,
|
||||
i32: encodeI32,
|
||||
i64: encodeI64,
|
||||
i128: encodeI128,
|
||||
f32: encodeF32,
|
||||
f64: encodeF64,
|
||||
dec: encodeDec,
|
||||
bool: encodeBool,
|
||||
string: encodeString,
|
||||
list: encodeList,
|
||||
record: encodeRecord,
|
||||
tuple: encodeTuple,
|
||||
tag: encodeTag,
|
||||
},
|
||||
DecoderFormatting {
|
||||
u8: decodeU8,
|
||||
u16: decodeU16,
|
||||
u32: decodeU32,
|
||||
u64: decodeU64,
|
||||
u128: decodeU128,
|
||||
i8: decodeI8,
|
||||
i16: decodeI16,
|
||||
i32: decodeI32,
|
||||
i64: decodeI64,
|
||||
i128: decodeI128,
|
||||
f32: decodeF32,
|
||||
f64: decodeF64,
|
||||
dec: decodeDec,
|
||||
bool: decodeBool,
|
||||
string: decodeString,
|
||||
list: decodeList,
|
||||
record: decodeRecord,
|
||||
tuple: decodeTuple,
|
||||
},
|
||||
]
|
||||
implements [
|
||||
EncoderFormatting {
|
||||
u8: encodeU8,
|
||||
u16: encodeU16,
|
||||
u32: encodeU32,
|
||||
u64: encodeU64,
|
||||
u128: encodeU128,
|
||||
i8: encodeI8,
|
||||
i16: encodeI16,
|
||||
i32: encodeI32,
|
||||
i64: encodeI64,
|
||||
i128: encodeI128,
|
||||
f32: encodeF32,
|
||||
f64: encodeF64,
|
||||
dec: encodeDec,
|
||||
bool: encodeBool,
|
||||
string: encodeString,
|
||||
list: encodeList,
|
||||
record: encodeRecord,
|
||||
tuple: encodeTuple,
|
||||
tag: encodeTag,
|
||||
},
|
||||
DecoderFormatting {
|
||||
u8: decodeU8,
|
||||
u16: decodeU16,
|
||||
u32: decodeU32,
|
||||
u64: decodeU64,
|
||||
u128: decodeU128,
|
||||
i8: decodeI8,
|
||||
i16: decodeI16,
|
||||
i32: decodeI32,
|
||||
i64: decodeI64,
|
||||
i128: decodeI128,
|
||||
f32: decodeF32,
|
||||
f64: decodeF64,
|
||||
dec: decodeDec,
|
||||
bool: decodeBool,
|
||||
string: decodeString,
|
||||
list: decodeList,
|
||||
record: decodeRecord,
|
||||
tuple: decodeTuple,
|
||||
},
|
||||
]
|
||||
|
||||
## Returns a JSON `Encoder` and `Decoder`
|
||||
json = @Json { fieldNameMapping: Default }
|
||||
|
|
|
|||
|
|
@ -339,8 +339,8 @@ 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_REPEAT: &str = "roc_builtins.str.repeat";
|
||||
pub const STR_TRIM: &str = "roc_builtins.str.trim";
|
||||
pub const STR_TRIM_LEFT: &str = "roc_builtins.str.trim_left";
|
||||
pub const STR_TRIM_RIGHT: &str = "roc_builtins.str.trim_right";
|
||||
pub const STR_TRIM_START: &str = "roc_builtins.str.trim_start";
|
||||
pub const STR_TRIM_END: &str = "roc_builtins.str.trim_end";
|
||||
pub const STR_GET_UNSAFE: &str = "roc_builtins.str.get_unsafe";
|
||||
pub const STR_RESERVE: &str = "roc_builtins.str.reserve";
|
||||
pub const STR_APPEND_SCALAR: &str = "roc_builtins.str.append_scalar";
|
||||
|
|
@ -393,8 +393,10 @@ pub const UTILS_TEST_PANIC: &str = "roc_builtins.utils.test_panic";
|
|||
pub const UTILS_ALLOCATE_WITH_REFCOUNT: &str = "roc_builtins.utils.allocate_with_refcount";
|
||||
pub const UTILS_INCREF_RC_PTR: &str = "roc_builtins.utils.incref_rc_ptr";
|
||||
pub const UTILS_DECREF_RC_PTR: &str = "roc_builtins.utils.decref_rc_ptr";
|
||||
pub const UTILS_FREE_RC_PTR: &str = "roc_builtins.utils.free_rc_ptr";
|
||||
pub const UTILS_INCREF_DATA_PTR: &str = "roc_builtins.utils.incref_data_ptr";
|
||||
pub const UTILS_DECREF_DATA_PTR: &str = "roc_builtins.utils.decref_data_ptr";
|
||||
pub const UTILS_FREE_DATA_PTR: &str = "roc_builtins.utils.free_data_ptr";
|
||||
pub const UTILS_IS_UNIQUE: &str = "roc_builtins.utils.is_unique";
|
||||
pub const UTILS_DECREF_CHECK_NULL: &str = "roc_builtins.utils.decref_check_null";
|
||||
pub const UTILS_DICT_PSEUDO_SEED: &str = "roc_builtins.utils.dict_pseudo_seed";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue