Merge remote-tracking branch 'origin/trunk' into backpassing

This commit is contained in:
Folkert 2021-03-06 19:01:39 +01:00
commit dbb4758cb9
40 changed files with 3279 additions and 2638 deletions

84
Cargo.lock generated
View file

@ -1690,7 +1690,7 @@ dependencies = [
"kernel32-sys", "kernel32-sys",
"libc", "libc",
"log", "log",
"miow 0.2.2", "miow",
"net2", "net2",
"slab", "slab",
"winapi 0.2.8", "winapi 0.2.8",
@ -1708,29 +1708,6 @@ dependencies = [
"slab", "slab",
] ]
[[package]]
name = "mio-named-pipes"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0840c1c50fd55e521b247f949c241c9997709f23bd7f023b9762cd561e935656"
dependencies = [
"log",
"mio",
"miow 0.3.6",
"winapi 0.3.9",
]
[[package]]
name = "mio-uds"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0"
dependencies = [
"iovec",
"libc",
"mio",
]
[[package]] [[package]]
name = "miow" name = "miow"
version = "0.2.2" version = "0.2.2"
@ -1743,16 +1720,6 @@ dependencies = [
"ws2_32-sys", "ws2_32-sys",
] ]
[[package]]
name = "miow"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a33c1b55807fbed163481b5ba66db4b2fa6cde694a5027be10fb724206c5897"
dependencies = [
"socket2",
"winapi 0.3.9",
]
[[package]] [[package]]
name = "naga" name = "naga"
version = "0.2.0" version = "0.2.0"
@ -2738,7 +2705,6 @@ dependencies = [
"pretty_assertions", "pretty_assertions",
"quickcheck", "quickcheck",
"quickcheck_macros", "quickcheck_macros",
"roc_can",
"roc_collections", "roc_collections",
"roc_module", "roc_module",
"roc_region", "roc_region",
@ -2811,7 +2777,6 @@ dependencies = [
"strip-ansi-escapes", "strip-ansi-escapes",
"target-lexicon", "target-lexicon",
"tempfile", "tempfile",
"tokio",
] ]
[[package]] [[package]]
@ -2859,9 +2824,7 @@ dependencies = [
"im", "im",
"im-rc", "im-rc",
"indoc", "indoc",
"inkwell",
"inlinable_string", "inlinable_string",
"itertools 0.9.0",
"libc", "libc",
"log", "log",
"maplit", "maplit",
@ -2873,25 +2836,16 @@ dependencies = [
"quickcheck", "quickcheck",
"quickcheck_macros", "quickcheck_macros",
"rand 0.8.3", "rand 0.8.3",
"roc_builtins",
"roc_can", "roc_can",
"roc_collections", "roc_collections",
"roc_constrain",
"roc_fmt", "roc_fmt",
"roc_gen",
"roc_load",
"roc_module", "roc_module",
"roc_mono",
"roc_parse", "roc_parse",
"roc_problem", "roc_problem",
"roc_region", "roc_region",
"roc_reporting",
"roc_solve",
"roc_types", "roc_types",
"roc_unify",
"ropey", "ropey",
"snafu", "snafu",
"target-lexicon",
"ven_graph", "ven_graph",
"wgpu", "wgpu",
"wgpu_glyph", "wgpu_glyph",
@ -2915,9 +2869,7 @@ dependencies = [
"roc_collections", "roc_collections",
"roc_module", "roc_module",
"roc_parse", "roc_parse",
"roc_problem",
"roc_region", "roc_region",
"roc_types",
] ]
[[package]] [[package]]
@ -2932,7 +2884,6 @@ dependencies = [
"inkwell", "inkwell",
"inlinable_string", "inlinable_string",
"libc", "libc",
"libloading",
"maplit", "maplit",
"pretty_assertions", "pretty_assertions",
"quickcheck", "quickcheck",
@ -2941,7 +2892,6 @@ dependencies = [
"roc_builtins", "roc_builtins",
"roc_can", "roc_can",
"roc_collections", "roc_collections",
"roc_constrain",
"roc_load", "roc_load",
"roc_module", "roc_module",
"roc_mono", "roc_mono",
@ -3054,7 +3004,6 @@ dependencies = [
"roc_builtins", "roc_builtins",
"roc_can", "roc_can",
"roc_collections", "roc_collections",
"roc_constrain",
"roc_load", "roc_load",
"roc_module", "roc_module",
"roc_parse", "roc_parse",
@ -3144,7 +3093,6 @@ dependencies = [
"roc_builtins", "roc_builtins",
"roc_can", "roc_can",
"roc_collections", "roc_collections",
"roc_constrain",
"roc_load", "roc_load",
"roc_module", "roc_module",
"roc_parse", "roc_parse",
@ -3175,8 +3123,6 @@ dependencies = [
"quickcheck_macros", "quickcheck_macros",
"roc_collections", "roc_collections",
"roc_module", "roc_module",
"roc_parse",
"roc_problem",
"roc_region", "roc_region",
"ven_ena", "ven_ena",
] ]
@ -3405,15 +3351,6 @@ dependencies = [
"opaque-debug", "opaque-debug",
] ]
[[package]]
name = "signal-hook-registry"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "siphasher" name = "siphasher"
version = "0.3.3" version = "0.3.3"
@ -3507,17 +3444,6 @@ dependencies = [
"syn 1.0.60", "syn 1.0.60",
] ]
[[package]]
name = "socket2"
version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e"
dependencies = [
"cfg-if 1.0.0",
"libc",
"winapi 0.3.9",
]
[[package]] [[package]]
name = "spirv_cross" name = "spirv_cross"
version = "0.22.2" version = "0.22.2"
@ -3666,7 +3592,6 @@ dependencies = [
"libc", "libc",
"libloading", "libloading",
"maplit", "maplit",
"pretty_assertions",
"quickcheck", "quickcheck",
"quickcheck_macros", "quickcheck_macros",
"roc_build", "roc_build",
@ -3763,17 +3688,10 @@ checksum = "6703a273949a90131b290be1fe7b039d0fc884aa1935860dfcbe056f28cd8092"
dependencies = [ dependencies = [
"bytes", "bytes",
"fnv", "fnv",
"lazy_static",
"libc",
"memchr", "memchr",
"mio",
"mio-named-pipes",
"mio-uds",
"num_cpus", "num_cpus",
"pin-project-lite 0.1.11", "pin-project-lite 0.1.11",
"signal-hook-registry",
"slab", "slab",
"winapi 0.3.9",
] ]
[[package]] [[package]]

View file

@ -58,7 +58,6 @@ im = "14" # im and im-rc should always have the same version!
im-rc = "14" # im and im-rc should always have the same version! im-rc = "14" # im and im-rc should always have the same version!
bumpalo = { version = "3.2", features = ["collections"] } bumpalo = { version = "3.2", features = ["collections"] }
inlinable_string = "0.1" inlinable_string = "0.1"
tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded", "process", "io-driver"] }
libc = "0.2" libc = "0.2"
libloading = "0.6" libloading = "0.6"

View file

@ -246,7 +246,7 @@ mod cli_run {
&example_file("benchmarks", "TestBase64.roc"), &example_file("benchmarks", "TestBase64.roc"),
"test-base64", "test-base64",
&[], &[],
"SGVsbG8gV29ybGQ=\n", "encoded: SGVsbG8gV29ybGQ=\ndecoded: Hello World\n",
true, true,
); );
} }

View file

@ -36,7 +36,14 @@ pub fn gen_from_mono_module(
let code_gen_start = SystemTime::now(); let code_gen_start = SystemTime::now();
for (home, (module_path, src)) in loaded.sources { for (home, (module_path, src)) in loaded.sources {
let src_lines: Vec<&str> = src.split('\n').collect(); let mut src_lines: Vec<&str> = Vec::new();
if let Some((_, header_src)) = loaded.header_sources.get(&home) {
src_lines.extend(header_src.split('\n'));
src_lines.extend(src.split('\n').skip(1));
} else {
src_lines.extend(src.split('\n'));
}
let palette = DEFAULT_PALETTE; let palette = DEFAULT_PALETTE;
// Report parsing and canonicalization problems // Report parsing and canonicalization problems

View file

@ -10,8 +10,6 @@ roc_collections = { path = "../collections" }
roc_region = { path = "../region" } roc_region = { path = "../region" }
roc_module = { path = "../module" } roc_module = { path = "../module" }
roc_types = { path = "../types" } roc_types = { path = "../types" }
roc_can = { path = "../can" }
[dev-dependencies] [dev-dependencies]
pretty_assertions = "0.5.1" pretty_assertions = "0.5.1"
maplit = "1.0.1" maplit = "1.0.1"

View file

@ -86,21 +86,22 @@ pub const RocList = extern struct {
const old_length = self.length; const old_length = self.length;
const delta_length = new_length - old_length; const delta_length = new_length - old_length;
const data_bytes = new_capacity * slot_size; const data_bytes = new_length * element_width;
const first_slot = allocateWithRefcount(allocator, alignment, data_bytes); const first_slot = utils.allocateWithRefcount(allocator, alignment, data_bytes);
// transfer the memory // transfer the memory
if (self.bytes) |source_ptr| { if (self.bytes) |source_ptr| {
const dest_ptr = first_slot; const dest_ptr = first_slot;
@memcpy(dest_ptr, source_ptr, old_length); @memcpy(dest_ptr, source_ptr, old_length * element_width);
@memset(dest_ptr + old_length * element_width, 0, delta_length * element_width);
} }
// NOTE the newly added elements are left uninitialized // NOTE the newly added elements are left uninitialized
const result = RocList{ const result = RocList{
.dict_bytes = first_slot, .bytes = first_slot,
.length = new_length, .length = new_length,
}; };
@ -152,6 +153,66 @@ pub fn listMapWithIndex(list: RocList, transform: Opaque, caller: Caller2, align
} }
} }
pub fn listMap2(list1: RocList, list2: RocList, transform: Opaque, caller: Caller2, alignment: usize, a_width: usize, b_width: usize, c_width: usize, dec_a: Dec, dec_b: Dec) callconv(.C) RocList {
const output_length = std.math.min(list1.len(), list2.len());
if (list1.bytes) |source_a| {
if (list2.bytes) |source_b| {
const output = RocList.allocate(std.heap.c_allocator, alignment, output_length, c_width);
const target_ptr = output.bytes orelse unreachable;
var i: usize = 0;
while (i < output_length) : (i += 1) {
const element_a = source_a + i * a_width;
const element_b = source_b + i * b_width;
const target = target_ptr + i * c_width;
caller(transform, element_a, element_b, target);
}
// if the lists don't have equal length, we must consume the remaining elements
// In this case we consume by (recursively) decrementing the elements
if (list1.len() > output_length) {
while (i < list1.len()) : (i += 1) {
const element_a = source_a + i * a_width;
dec_a(element_a);
}
} else if (list2.len() > output_length) {
while (i < list2.len()) : (i += 1) {
const element_b = source_b + i * b_width;
dec_b(element_b);
}
}
utils.decref(std.heap.c_allocator, alignment, list1.bytes, list1.len() * a_width);
utils.decref(std.heap.c_allocator, alignment, list2.bytes, list2.len() * b_width);
return output;
} else {
// consume list1 elements (we know there is at least one because the list1.bytes pointer is non-null
var i: usize = 0;
while (i < list1.len()) : (i += 1) {
const element_a = source_a + i * a_width;
dec_a(element_a);
}
utils.decref(std.heap.c_allocator, alignment, list1.bytes, list1.len() * a_width);
return RocList.empty();
}
} else {
// consume list2 elements (if any)
if (list2.bytes) |source_b| {
var i: usize = 0;
while (i < list2.len()) : (i += 1) {
const element_b = source_b + i * b_width;
dec_b(element_b);
}
utils.decref(std.heap.c_allocator, alignment, list2.bytes, list2.len() * b_width);
}
return RocList.empty();
}
}
pub fn listKeepIf(list: RocList, transform: Opaque, caller: Caller1, alignment: usize, element_width: usize, inc: Inc, dec: Dec) callconv(.C) RocList { pub fn listKeepIf(list: RocList, transform: Opaque, caller: Caller1, alignment: usize, element_width: usize, inc: Inc, dec: Dec) callconv(.C) RocList {
if (list.bytes) |source_ptr| { if (list.bytes) |source_ptr| {
const size = list.len(); const size = list.len();
@ -246,19 +307,35 @@ pub fn listWalk(list: RocList, stepper: Opaque, stepper_caller: Caller2, accum:
return; return;
} }
@memcpy(output orelse unreachable, accum orelse unreachable, accum_width); if (list.isEmpty()) {
@memcpy(output orelse unreachable, accum orelse unreachable, accum_width);
return;
}
const alloc: [*]u8 = @ptrCast([*]u8, std.heap.c_allocator.alloc(u8, accum_width) catch unreachable);
var b1 = output orelse unreachable;
var b2 = alloc;
@memcpy(b2, accum orelse unreachable, accum_width);
if (list.bytes) |source_ptr| { if (list.bytes) |source_ptr| {
var i: usize = 0; var i: usize = 0;
const size = list.len(); const size = list.len();
while (i < size) : (i += 1) { while (i < size) : (i += 1) {
const element = source_ptr + i * element_width; const element = source_ptr + i * element_width;
stepper_caller(stepper, element, output, output); stepper_caller(stepper, element, b2, b1);
}
const data_bytes = list.len() * element_width; const temp = b1;
utils.decref(std.heap.c_allocator, alignment, list.bytes, data_bytes); b2 = b1;
b1 = temp;
}
} }
@memcpy(output orelse unreachable, b2, accum_width);
std.heap.c_allocator.free(alloc[0..accum_width]);
const data_bytes = list.len() * element_width;
utils.decref(std.heap.c_allocator, alignment, list.bytes, data_bytes);
} }
pub fn listWalkBackwards(list: RocList, stepper: Opaque, stepper_caller: Caller2, accum: Opaque, alignment: usize, element_width: usize, accum_width: usize, output: Opaque) callconv(.C) void { pub fn listWalkBackwards(list: RocList, stepper: Opaque, stepper_caller: Caller2, accum: Opaque, alignment: usize, element_width: usize, accum_width: usize, output: Opaque) callconv(.C) void {
@ -266,7 +343,16 @@ pub fn listWalkBackwards(list: RocList, stepper: Opaque, stepper_caller: Caller2
return; return;
} }
@memcpy(output orelse unreachable, accum orelse unreachable, accum_width); if (list.isEmpty()) {
@memcpy(output orelse unreachable, accum orelse unreachable, accum_width);
return;
}
const alloc: [*]u8 = @ptrCast([*]u8, std.heap.c_allocator.alloc(u8, accum_width) catch unreachable);
var b1 = output orelse unreachable;
var b2 = alloc;
@memcpy(b2, accum orelse unreachable, accum_width);
if (list.bytes) |source_ptr| { if (list.bytes) |source_ptr| {
const size = list.len(); const size = list.len();
@ -274,12 +360,22 @@ pub fn listWalkBackwards(list: RocList, stepper: Opaque, stepper_caller: Caller2
while (i > 0) { while (i > 0) {
i -= 1; i -= 1;
const element = source_ptr + i * element_width; const element = source_ptr + i * element_width;
stepper_caller(stepper, element, output, output); stepper_caller(stepper, element, b2, b1);
const temp = b1;
b2 = b1;
b1 = temp;
} }
const data_bytes = list.len() * element_width; const data_bytes = list.len() * element_width;
utils.decref(std.heap.c_allocator, alignment, list.bytes, data_bytes); utils.decref(std.heap.c_allocator, alignment, list.bytes, data_bytes);
} }
@memcpy(output orelse unreachable, b2, accum_width);
std.heap.c_allocator.free(alloc[0..accum_width]);
const data_bytes = list.len() * element_width;
utils.decref(std.heap.c_allocator, alignment, list.bytes, data_bytes);
} }
// List.contains : List k, k -> Bool // List.contains : List k, k -> Bool
@ -324,3 +420,16 @@ pub fn listRepeat(count: usize, alignment: usize, element: Opaque, element_width
unreachable; unreachable;
} }
} }
pub fn listAppend(list: RocList, alignment: usize, element: Opaque, element_width: usize) callconv(.C) RocList {
const old_length = list.len();
var output = list.reallocate(std.heap.c_allocator, alignment, old_length + 1, element_width);
if (output.bytes) |target| {
if (element) |source| {
@memcpy(target + old_length * element_width, source, element_width);
}
}
return output;
}

View file

@ -7,6 +7,7 @@ const list = @import("list.zig");
comptime { comptime {
exportListFn(list.listMap, "map"); exportListFn(list.listMap, "map");
exportListFn(list.listMap2, "map2");
exportListFn(list.listMapWithIndex, "map_with_index"); exportListFn(list.listMapWithIndex, "map_with_index");
exportListFn(list.listKeepIf, "keep_if"); exportListFn(list.listKeepIf, "keep_if");
exportListFn(list.listWalk, "walk"); exportListFn(list.listWalk, "walk");
@ -15,6 +16,7 @@ comptime {
exportListFn(list.listKeepErrs, "keep_errs"); exportListFn(list.listKeepErrs, "keep_errs");
exportListFn(list.listContains, "contains"); exportListFn(list.listContains, "contains");
exportListFn(list.listRepeat, "repeat"); exportListFn(list.listRepeat, "repeat");
exportListFn(list.listAppend, "append");
} }
// Dict Module // Dict Module

View file

@ -63,6 +63,7 @@ pub const DICT_WALK: &str = "roc_builtins.dict.walk";
pub const SET_FROM_LIST: &str = "roc_builtins.dict.set_from_list"; pub const SET_FROM_LIST: &str = "roc_builtins.dict.set_from_list";
pub const LIST_MAP: &str = "roc_builtins.list.map"; pub const LIST_MAP: &str = "roc_builtins.list.map";
pub const LIST_MAP2: &str = "roc_builtins.list.map2";
pub const LIST_MAP_WITH_INDEX: &str = "roc_builtins.list.map_with_index"; pub const LIST_MAP_WITH_INDEX: &str = "roc_builtins.list.map_with_index";
pub const LIST_KEEP_IF: &str = "roc_builtins.list.keep_if"; pub const LIST_KEEP_IF: &str = "roc_builtins.list.keep_if";
pub const LIST_KEEP_OKS: &str = "roc_builtins.list.keep_oks"; pub const LIST_KEEP_OKS: &str = "roc_builtins.list.keep_oks";
@ -71,3 +72,4 @@ pub const LIST_WALK: &str = "roc_builtins.list.walk";
pub const LIST_WALK_BACKWARDS: &str = "roc_builtins.list.walk_backwards"; pub const LIST_WALK_BACKWARDS: &str = "roc_builtins.list.walk_backwards";
pub const LIST_CONTAINS: &str = "roc_builtins.list.contains"; pub const LIST_CONTAINS: &str = "roc_builtins.list.contains";
pub const LIST_REPEAT: &str = "roc_builtins.list.repeat"; pub const LIST_REPEAT: &str = "roc_builtins.list.repeat";
pub const LIST_APPEND: &str = "roc_builtins.list.append";

View file

@ -804,6 +804,19 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
) )
}); });
// map2 : List a, List b, (a, b -> c) -> List c
add_type(Symbol::LIST_MAP2, {
let_tvars! {a, b, c, cvar};
top_level_function(
vec![
list_type(flex(a)),
list_type(flex(b)),
closure(vec![flex(a), flex(b)], cvar, Box::new(flex(c))),
],
Box::new(list_type(flex(c))),
)
});
// append : List elem, elem -> List elem // append : List elem, elem -> List elem
add_type( add_type(
Symbol::LIST_APPEND, Symbol::LIST_APPEND,

View file

@ -80,6 +80,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
LIST_PREPEND => list_prepend, LIST_PREPEND => list_prepend,
LIST_JOIN => list_join, LIST_JOIN => list_join,
LIST_MAP => list_map, LIST_MAP => list_map,
LIST_MAP2 => list_map2,
LIST_MAP_WITH_INDEX => list_map_with_index, LIST_MAP_WITH_INDEX => list_map_with_index,
LIST_KEEP_IF => list_keep_if, LIST_KEEP_IF => list_keep_if,
LIST_KEEP_OKS => list_keep_oks, LIST_KEEP_OKS => list_keep_oks,
@ -216,6 +217,7 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap<Symbol, Def> {
Symbol::LIST_PREPEND => list_prepend, Symbol::LIST_PREPEND => list_prepend,
Symbol::LIST_JOIN => list_join, Symbol::LIST_JOIN => list_join,
Symbol::LIST_MAP => list_map, Symbol::LIST_MAP => list_map,
Symbol::LIST_MAP2 => list_map2,
Symbol::LIST_MAP_WITH_INDEX => list_map_with_index, Symbol::LIST_MAP_WITH_INDEX => list_map_with_index,
Symbol::LIST_KEEP_IF => list_keep_if, Symbol::LIST_KEEP_IF => list_keep_if,
Symbol::LIST_KEEP_OKS => list_keep_oks, Symbol::LIST_KEEP_OKS => list_keep_oks,
@ -2115,6 +2117,11 @@ fn list_map_with_index(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_2(symbol, LowLevel::ListMapWithIndex, var_store) lowlevel_2(symbol, LowLevel::ListMapWithIndex, var_store)
} }
/// List.map2 : List a, List b, (a, b -> c) -> List c
fn list_map2(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_3(symbol, LowLevel::ListMap2, var_store)
}
/// Dict.hashTestOnly : k, v -> Nat /// Dict.hashTestOnly : k, v -> Nat
pub fn dict_hash_test_only(symbol: Symbol, var_store: &mut VarStore) -> Def { pub fn dict_hash_test_only(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_2(symbol, LowLevel::Hash, var_store) lowlevel_2(symbol, LowLevel::Hash, var_store)

View file

@ -122,8 +122,19 @@ where
} else { } else {
// This is a type alias // This is a type alias
// the should already be added to the scope when this module is canonicalized // the symbol should already be added to the scope when this module is canonicalized
debug_assert!(scope.contains_alias(symbol)); debug_assert!(scope.contains_alias(symbol));
// but now we know this symbol by a different identifier, so we still need to add it to
// the scope
match scope.import(ident, symbol, region) {
Ok(()) => {
// here we do nothing special
}
Err((_shadowed_symbol, _region)) => {
panic!("TODO gracefully handle shadowing in imports.")
}
}
} }
} }

View file

@ -10,8 +10,6 @@ roc_collections = { path = "../collections" }
roc_region = { path = "../region" } roc_region = { path = "../region" }
roc_module = { path = "../module" } roc_module = { path = "../module" }
roc_parse = { path = "../parse" } roc_parse = { path = "../parse" }
roc_problem = { path = "../problem" }
roc_types = { path = "../types" }
im = "14" # im and im-rc should always have the same version! im = "14" # im and im-rc should always have the same version!
im-rc = "14" # im and im-rc should always have the same version! im-rc = "14" # im and im-rc should always have the same version!
bumpalo = { version = "3.2", features = ["collections"] } bumpalo = { version = "3.2", features = ["collections"] }

View file

@ -12,7 +12,6 @@ roc_module = { path = "../module" }
roc_problem = { path = "../problem" } roc_problem = { path = "../problem" }
roc_types = { path = "../types" } roc_types = { path = "../types" }
roc_builtins = { path = "../builtins" } roc_builtins = { path = "../builtins" }
roc_constrain = { path = "../constrain" }
roc_unify = { path = "../unify" } roc_unify = { path = "../unify" }
roc_solve = { path = "../solve" } roc_solve = { path = "../solve" }
roc_mono = { path = "../mono" } roc_mono = { path = "../mono" }
@ -40,7 +39,6 @@ either = "1.6.1"
# This way, GitHub Actions works and nobody's builds get broken. # This way, GitHub Actions works and nobody's builds get broken.
inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release3" } inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release3" }
target-lexicon = "0.10" target-lexicon = "0.10"
libloading = "0.6"
[dev-dependencies] [dev-dependencies]
roc_can = { path = "../can" } roc_can = { path = "../can" }

View file

@ -7,8 +7,8 @@ use crate::llvm::build_hash::generic_hash;
use crate::llvm::build_list::{ use crate::llvm::build_list::{
allocate_list, empty_list, empty_polymorphic_list, list_append, list_concat, list_contains, allocate_list, empty_list, empty_polymorphic_list, list_append, list_concat, list_contains,
list_get_unsafe, list_join, list_keep_errs, list_keep_if, list_keep_oks, list_len, list_map, list_get_unsafe, list_join, list_keep_errs, list_keep_if, list_keep_oks, list_len, list_map,
list_map_with_index, list_prepend, list_repeat, list_reverse, list_set, list_single, list_sum, list_map2, list_map_with_index, list_prepend, list_repeat, list_reverse, list_set, list_single,
list_walk, list_walk_backwards, list_sum, list_walk, list_walk_backwards,
}; };
use crate::llvm::build_str::{ use crate::llvm::build_str::{
str_concat, str_count_graphemes, str_ends_with, str_from_float, str_from_int, str_from_utf8, str_concat, str_count_graphemes, str_ends_with, str_from_float, str_from_int, str_from_utf8,
@ -1104,7 +1104,7 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
let tag_field_layouts = &fields[*tag_id as usize]; let tag_field_layouts = &fields[*tag_id as usize];
for (field_symbol, tag_field_layout) in arguments.iter().zip(tag_field_layouts.iter()) { for (field_symbol, tag_field_layout) in arguments.iter().zip(tag_field_layouts.iter()) {
let (val, val_layout) = load_symbol_and_layout(scope, field_symbol); let (val, _val_layout) = load_symbol_and_layout(scope, field_symbol);
// Zero-sized fields have no runtime representation. // Zero-sized fields have no runtime representation.
// The layout of the struct expects them to be dropped! // The layout of the struct expects them to be dropped!
@ -1127,7 +1127,7 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
field_vals.push(ptr); field_vals.push(ptr);
} else { } else {
// this check fails for recursive tag unions, but can be helpful while debugging // this check fails for recursive tag unions, but can be helpful while debugging
debug_assert_eq!(tag_field_layout, val_layout); // debug_assert_eq!(tag_field_layout, val_layout);
field_vals.push(val); field_vals.push(val);
} }
@ -3719,6 +3719,33 @@ fn run_low_level<'a, 'ctx, 'env>(
_ => unreachable!("invalid list layout"), _ => unreachable!("invalid list layout"),
} }
} }
ListMap2 => {
debug_assert_eq!(args.len(), 3);
let (list1, list1_layout) = load_symbol_and_layout(scope, &args[0]);
let (list2, list2_layout) = load_symbol_and_layout(scope, &args[1]);
let (func, func_layout) = load_symbol_and_layout(scope, &args[2]);
match (list1_layout, list2_layout) {
(
Layout::Builtin(Builtin::List(_, element1_layout)),
Layout::Builtin(Builtin::List(_, element2_layout)),
) => list_map2(
env,
layout_ids,
func,
func_layout,
list1,
list2,
element1_layout,
element2_layout,
),
(Layout::Builtin(Builtin::EmptyList), _)
| (_, Layout::Builtin(Builtin::EmptyList)) => empty_list(env),
_ => unreachable!("invalid list layout"),
}
}
ListMapWithIndex => { ListMapWithIndex => {
// List.map : List before, (before -> after) -> List after // List.map : List before, (before -> after) -> List after
debug_assert_eq!(args.len(), 2); debug_assert_eq!(args.len(), 2);

View file

@ -551,63 +551,51 @@ pub fn list_get_unsafe<'a, 'ctx, 'env>(
/// List.append : List elem, elem -> List elem /// List.append : List elem, elem -> List elem
pub fn list_append<'a, 'ctx, 'env>( pub fn list_append<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
inplace: InPlace, _inplace: InPlace,
original_wrapper: StructValue<'ctx>, original_wrapper: StructValue<'ctx>,
elem: BasicValueEnum<'ctx>, element: BasicValueEnum<'ctx>,
elem_layout: &Layout<'a>, element_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
let builder = env.builder; let builder = env.builder;
let ctx = env.context;
// Load the usize length from the wrapper. let list_i128 = complex_bitcast(
let list_len = list_len(builder, original_wrapper); env.builder,
let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); original_wrapper.into(),
let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); env.context.i128_type().into(),
"to_i128",
let list_ptr = load_list_ptr(builder, original_wrapper, ptr_type);
// The output list length, which is the old list length + 1
let new_list_len = env.builder.build_int_add(
ctx.i64_type().const_int(1_u64, false),
list_len,
"new_list_length",
); );
let ptr_bytes = env.ptr_bytes; let element_width = env
// Calculate the number of bytes we'll need to allocate.
let elem_bytes = env
.ptr_int() .ptr_int()
.const_int(elem_layout.stack_size(env.ptr_bytes) as u64, false); .const_int(element_layout.stack_size(env.ptr_bytes) as u64, false);
// This is the size of the list coming in, before we have added an element let element_ptr = builder.build_alloca(element.get_type(), "element");
// to the end. builder.build_store(element_ptr, element);
let list_size = env
.builder
.build_int_mul(elem_bytes, list_len, "mul_old_len_by_elem_bytes");
// Allocate space for the new array that we'll copy into. let alignment = element_layout.alignment_bytes(env.ptr_bytes);
let clone_ptr = allocate_list(env, inplace, elem_layout, new_list_len); let alignment_iv = env.ptr_int().const_int(alignment as u64, false);
// TODO check if malloc returned null; if so, runtime error for OOM! let output = call_bitcode_fn(
env,
&[
list_i128,
alignment_iv.into(),
builder.build_bitcast(
element_ptr,
env.context.i8_type().ptr_type(AddressSpace::Generic),
"to_opaque",
),
element_width.into(),
],
&bitcode::LIST_APPEND,
);
if elem_layout.safe_to_memcpy() { complex_bitcast(
// Copy the bytes from the original array into the new env.builder,
// one we just malloc'd. output,
// collection(env.context, env.ptr_bytes).into(),
// TODO how do we decide when to do the small memcpy vs the normal one? "from_i128",
builder )
.build_memcpy(clone_ptr, ptr_bytes, list_ptr, ptr_bytes, list_size)
.unwrap();
} else {
panic!("TODO Cranelift currently only knows how to clone list elements that are Copy.");
}
let elem_ptr = unsafe { builder.build_in_bounds_gep(clone_ptr, &[list_len], "load_index") };
builder.build_store(elem_ptr, elem);
store_list(env, clone_ptr, new_list_len)
} }
/// List.set : List elem, Int, elem -> List elem /// List.set : List elem, Int, elem -> List elem
@ -1230,6 +1218,93 @@ fn list_map_generic<'a, 'ctx, 'env>(
) )
} }
pub fn list_map2<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
transform: BasicValueEnum<'ctx>,
transform_layout: &Layout<'a>,
list1: BasicValueEnum<'ctx>,
list2: BasicValueEnum<'ctx>,
element1_layout: &Layout<'a>,
element2_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
let return_layout = match transform_layout {
Layout::FunctionPointer(_, ret) => ret,
Layout::Closure(_, _, ret) => ret,
_ => unreachable!("not a callable layout"),
};
let u8_ptr = env.context.i8_type().ptr_type(AddressSpace::Generic);
let list1_i128 = complex_bitcast(
env.builder,
list1,
env.context.i128_type().into(),
"to_i128",
);
let list2_i128 = complex_bitcast(
env.builder,
list2,
env.context.i128_type().into(),
"to_i128",
);
let transform_ptr = builder.build_alloca(transform.get_type(), "transform_ptr");
env.builder.build_store(transform_ptr, transform);
let argument_layouts = [element1_layout.clone(), element2_layout.clone()];
let stepper_caller =
build_transform_caller(env, layout_ids, transform_layout, &argument_layouts)
.as_global_value()
.as_pointer_value();
let a_width = env
.ptr_int()
.const_int(element1_layout.stack_size(env.ptr_bytes) as u64, false);
let b_width = env
.ptr_int()
.const_int(element2_layout.stack_size(env.ptr_bytes) as u64, false);
let c_width = env
.ptr_int()
.const_int(return_layout.stack_size(env.ptr_bytes) as u64, false);
let alignment = return_layout.alignment_bytes(env.ptr_bytes);
let alignment_iv = env.ptr_int().const_int(alignment as u64, false);
let dec_a = build_dec_wrapper(env, layout_ids, element1_layout);
let dec_b = build_dec_wrapper(env, layout_ids, element2_layout);
let output = call_bitcode_fn(
env,
&[
list1_i128,
list2_i128,
env.builder
.build_bitcast(transform_ptr, u8_ptr, "to_opaque"),
stepper_caller.into(),
alignment_iv.into(),
a_width.into(),
b_width.into(),
c_width.into(),
dec_a.as_global_value().as_pointer_value().into(),
dec_b.as_global_value().as_pointer_value().into(),
],
bitcode::LIST_MAP2,
);
complex_bitcast(
env.builder,
output,
collection(env.context, env.ptr_bytes).into(),
"from_i128",
)
}
/// List.concat : List elem, List elem -> List elem /// List.concat : List elem, List elem -> List elem
pub fn list_concat<'a, 'ctx, 'env>( pub fn list_concat<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,

View file

@ -366,6 +366,7 @@ struct ModuleCache<'a> {
mono_problems: MutMap<ModuleId, Vec<roc_mono::ir::MonoProblem>>, mono_problems: MutMap<ModuleId, Vec<roc_mono::ir::MonoProblem>>,
sources: MutMap<ModuleId, (PathBuf, &'a str)>, sources: MutMap<ModuleId, (PathBuf, &'a str)>,
header_sources: MutMap<ModuleId, (PathBuf, &'a str)>,
} }
fn start_phase<'a>(module_id: ModuleId, phase: Phase, state: &mut State<'a>) -> Vec<BuildTask<'a>> { fn start_phase<'a>(module_id: ModuleId, phase: Phase, state: &mut State<'a>) -> Vec<BuildTask<'a>> {
@ -616,6 +617,7 @@ pub struct LoadedModule {
pub type_problems: MutMap<ModuleId, Vec<solve::TypeError>>, pub type_problems: MutMap<ModuleId, Vec<solve::TypeError>>,
pub declarations_by_id: MutMap<ModuleId, Vec<Declaration>>, pub declarations_by_id: MutMap<ModuleId, Vec<Declaration>>,
pub exposed_to_host: MutMap<Symbol, Variable>, pub exposed_to_host: MutMap<Symbol, Variable>,
pub header_sources: MutMap<ModuleId, (PathBuf, Box<str>)>,
pub sources: MutMap<ModuleId, (PathBuf, Box<str>)>, pub sources: MutMap<ModuleId, (PathBuf, Box<str>)>,
pub timings: MutMap<ModuleId, ModuleTiming>, pub timings: MutMap<ModuleId, ModuleTiming>,
pub documentation: MutMap<ModuleId, ModuleDocumentation>, pub documentation: MutMap<ModuleId, ModuleDocumentation>,
@ -639,7 +641,8 @@ struct ModuleHeader<'a> {
package_qualified_imported_modules: MutSet<PackageQualified<'a, ModuleId>>, package_qualified_imported_modules: MutSet<PackageQualified<'a, ModuleId>>,
exposes: Vec<Symbol>, exposes: Vec<Symbol>,
exposed_imports: MutMap<Ident, (Symbol, Region)>, exposed_imports: MutMap<Ident, (Symbol, Region)>,
src: &'a [u8], header_src: &'a str,
parse_state: roc_parse::parser::State<'a>,
module_timing: ModuleTiming, module_timing: ModuleTiming,
} }
@ -698,6 +701,7 @@ pub struct MonomorphizedModule<'a> {
pub mono_problems: MutMap<ModuleId, Vec<roc_mono::ir::MonoProblem>>, pub mono_problems: MutMap<ModuleId, Vec<roc_mono::ir::MonoProblem>>,
pub procedures: MutMap<(Symbol, Layout<'a>), Proc<'a>>, pub procedures: MutMap<(Symbol, Layout<'a>), Proc<'a>>,
pub exposed_to_host: MutMap<Symbol, Variable>, pub exposed_to_host: MutMap<Symbol, Variable>,
pub header_sources: MutMap<ModuleId, (PathBuf, Box<str>)>,
pub sources: MutMap<ModuleId, (PathBuf, Box<str>)>, pub sources: MutMap<ModuleId, (PathBuf, Box<str>)>,
pub timings: MutMap<ModuleId, ModuleTiming>, pub timings: MutMap<ModuleId, ModuleTiming>,
} }
@ -914,26 +918,19 @@ impl ModuleTiming {
end_time, end_time,
} = self; } = self;
end_time let calculate = |t: Result<Duration, std::time::SystemTimeError>| -> Option<Duration> {
.duration_since(*start_time) t.ok()?
.ok() .checked_sub(*make_specializations)?
.and_then(|t| { .checked_sub(*find_specializations)?
t.checked_sub(*make_specializations).and_then(|t| { .checked_sub(*solve)?
t.checked_sub(*find_specializations).and_then(|t| { .checked_sub(*constrain)?
t.checked_sub(*solve).and_then(|t| { .checked_sub(*canonicalize)?
t.checked_sub(*constrain).and_then(|t| { .checked_sub(*parse_body)?
t.checked_sub(*canonicalize).and_then(|t| { .checked_sub(*parse_header)?
t.checked_sub(*parse_body).and_then(|t| { .checked_sub(*read_roc_file)
t.checked_sub(*parse_header) };
.and_then(|t| t.checked_sub(*read_roc_file))
}) calculate(end_time.duration_since(*start_time)).unwrap_or_else(Duration::default)
})
})
})
})
})
})
.unwrap_or_else(Duration::default)
} }
} }
@ -1678,6 +1675,11 @@ fn update<'a>(
.exposed_symbols_by_module .exposed_symbols_by_module
.insert(home, exposed_symbols); .insert(home, exposed_symbols);
state
.module_cache
.header_sources
.insert(home, (header.module_path.clone(), header.header_src));
state state
.module_cache .module_cache
.imports .imports
@ -2115,6 +2117,7 @@ fn finish_specialization(
type_problems, type_problems,
can_problems, can_problems,
sources, sources,
header_sources,
.. ..
} = module_cache; } = module_cache;
@ -2123,6 +2126,11 @@ fn finish_specialization(
.map(|(id, (path, src))| (id, (path, src.into()))) .map(|(id, (path, src))| (id, (path, src.into())))
.collect(); .collect();
let header_sources: MutMap<ModuleId, (PathBuf, Box<str>)> = header_sources
.into_iter()
.map(|(id, (path, src))| (id, (path, src.into())))
.collect();
let path_to_platform = { let path_to_platform = {
use PlatformPath::*; use PlatformPath::*;
let package_or_path = match platform_path { let package_or_path = match platform_path {
@ -2224,6 +2232,7 @@ fn finish_specialization(
interns, interns,
procedures, procedures,
sources, sources,
header_sources,
timings: state.timings, timings: state.timings,
}) })
} }
@ -2251,6 +2260,13 @@ fn finish(
.map(|(id, (path, src))| (id, (path, src.into()))) .map(|(id, (path, src))| (id, (path, src.into())))
.collect(); .collect();
let header_sources = state
.module_cache
.header_sources
.into_iter()
.map(|(id, (path, src))| (id, (path, src.into())))
.collect();
LoadedModule { LoadedModule {
module_id: state.root_id, module_id: state.root_id,
interns, interns,
@ -2259,6 +2275,7 @@ fn finish(
type_problems: state.module_cache.type_problems, type_problems: state.module_cache.type_problems,
declarations_by_id: state.declarations_by_id, declarations_by_id: state.declarations_by_id,
exposed_to_host: exposed_vars_by_symbol.into_iter().collect(), exposed_to_host: exposed_vars_by_symbol.into_iter().collect(),
header_sources,
sources, sources,
timings: state.timings, timings: state.timings,
documentation, documentation,
@ -2468,41 +2485,63 @@ fn parse_header<'a>(
module_timing.parse_header = parse_header_duration; module_timing.parse_header = parse_header_duration;
match parsed { match parsed {
Ok((_, ast::Module::Interface { header }, parse_state)) => Ok(send_header( Ok((_, ast::Module::Interface { header }, parse_state)) => {
Located { let header_src = unsafe {
region: header.name.region, let chomped = src_bytes.len() - parse_state.bytes.len();
value: ModuleNameEnum::Interface(header.name.value), std::str::from_utf8_unchecked(&src_bytes[..chomped])
}, };
filename,
is_root_module, let info = HeaderInfo {
opt_shorthand, loc_name: Located {
&[], region: header.name.region,
header.exposes.into_bump_slice(), value: ModuleNameEnum::Interface(header.name.value),
header.imports.into_bump_slice(), },
None, filename,
parse_state, is_root_module,
module_ids, opt_shorthand,
ident_ids_by_module, header_src,
module_timing, packages: &[],
)), exposes: header.exposes.into_bump_slice(),
imports: header.imports.into_bump_slice(),
to_platform: None,
};
Ok(send_header(
info,
parse_state,
module_ids,
ident_ids_by_module,
module_timing,
))
}
Ok((_, ast::Module::App { header }, parse_state)) => { Ok((_, ast::Module::App { header }, parse_state)) => {
let mut pkg_config_dir = filename.clone(); let mut pkg_config_dir = filename.clone();
pkg_config_dir.pop(); pkg_config_dir.pop();
let header_src = unsafe {
let chomped = src_bytes.len() - parse_state.bytes.len();
std::str::from_utf8_unchecked(&src_bytes[..chomped])
};
let packages = header.packages.into_bump_slice(); let packages = header.packages.into_bump_slice();
let (module_id, app_module_header_msg) = send_header( let info = HeaderInfo {
Located { loc_name: Located {
region: header.name.region, region: header.name.region,
value: ModuleNameEnum::App(header.name.value), value: ModuleNameEnum::App(header.name.value),
}, },
filename, filename,
is_root_module, is_root_module,
opt_shorthand, opt_shorthand,
header_src,
packages, packages,
header.provides.into_bump_slice(), exposes: header.provides.into_bump_slice(),
header.imports.into_bump_slice(), imports: header.imports.into_bump_slice(),
Some(header.to.value.clone()), to_platform: Some(header.to.value.clone()),
};
let (module_id, app_module_header_msg) = send_header(
info,
parse_state, parse_state,
module_ids.clone(), module_ids.clone(),
ident_ids_by_module.clone(), ident_ids_by_module.clone(),
@ -2673,16 +2712,22 @@ enum ModuleNameEnum<'a> {
PkgConfig, PkgConfig,
} }
#[allow(clippy::too_many_arguments)] #[derive(Debug)]
fn send_header<'a>( struct HeaderInfo<'a> {
loc_name: Located<ModuleNameEnum<'a>>, loc_name: Located<ModuleNameEnum<'a>>,
filename: PathBuf, filename: PathBuf,
is_root_module: bool, is_root_module: bool,
opt_shorthand: Option<&'a str>, opt_shorthand: Option<&'a str>,
header_src: &'a str,
packages: &'a [Located<PackageEntry<'a>>], packages: &'a [Located<PackageEntry<'a>>],
exposes: &'a [Located<ExposesEntry<'a, &'a str>>], exposes: &'a [Located<ExposesEntry<'a, &'a str>>],
imports: &'a [Located<ImportsEntry<'a>>], imports: &'a [Located<ImportsEntry<'a>>],
to_platform: Option<To<'a>>, to_platform: Option<To<'a>>,
}
#[allow(clippy::too_many_arguments)]
fn send_header<'a>(
info: HeaderInfo<'a>,
parse_state: parser::State<'a>, parse_state: parser::State<'a>,
module_ids: Arc<Mutex<PackageModuleIds<'a>>>, module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>, ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
@ -2690,6 +2735,18 @@ fn send_header<'a>(
) -> (ModuleId, Msg<'a>) { ) -> (ModuleId, Msg<'a>) {
use ModuleNameEnum::*; use ModuleNameEnum::*;
let HeaderInfo {
loc_name,
filename,
is_root_module,
opt_shorthand,
packages,
exposes,
imports,
to_platform,
header_src,
} = info;
let declared_name: ModuleName = match &loc_name.value { let declared_name: ModuleName = match &loc_name.value {
PkgConfig => unreachable!(), PkgConfig => unreachable!(),
App(_) => ModuleName::APP.into(), App(_) => ModuleName::APP.into(),
@ -2872,7 +2929,8 @@ fn send_header<'a>(
package_qualified_imported_modules, package_qualified_imported_modules,
deps_by_name, deps_by_name,
exposes: exposed, exposes: exposed,
src: parse_state.bytes, header_src,
parse_state,
exposed_imports: scope, exposed_imports: scope,
module_timing, module_timing,
}, },
@ -3091,7 +3149,8 @@ fn send_header_two<'a>(
package_qualified_imported_modules, package_qualified_imported_modules,
deps_by_name, deps_by_name,
exposes: exposed, exposes: exposed,
src: parse_state.bytes, header_src: "#builtin effect header",
parse_state,
exposed_imports: scope, exposed_imports: scope,
module_timing, module_timing,
}, },
@ -3619,12 +3678,13 @@ where
fn parse<'a>(arena: &'a Bump, header: ModuleHeader<'a>) -> Result<Msg<'a>, LoadingProblem<'a>> { fn parse<'a>(arena: &'a Bump, header: ModuleHeader<'a>) -> Result<Msg<'a>, LoadingProblem<'a>> {
let mut module_timing = header.module_timing; let mut module_timing = header.module_timing;
let parse_start = SystemTime::now(); let parse_start = SystemTime::now();
let parse_state = parser::State::new_in(arena, &header.src, Attempting::Module); let source = header.parse_state.bytes;
let parse_state = header.parse_state;
let parsed_defs = match module_defs().parse(&arena, parse_state) { let parsed_defs = match module_defs().parse(&arena, parse_state) {
Ok((_, success, _state)) => success, Ok((_, success, _state)) => success,
Err((_, fail, _)) => { Err((_, fail, _)) => {
return Err(LoadingProblem::ParsingFailed( return Err(LoadingProblem::ParsingFailed(
fail.into_parse_problem(header.module_path, header.src), fail.into_parse_problem(header.module_path, source),
)); ));
} }
}; };
@ -3642,7 +3702,7 @@ fn parse<'a>(arena: &'a Bump, header: ModuleHeader<'a>) -> Result<Msg<'a>, Loadi
// SAFETY: By this point we've already incrementally verified that there // SAFETY: By this point we've already incrementally verified that there
// are no UTF-8 errors in these bytes. If there had been any UTF-8 errors, // are no UTF-8 errors in these bytes. If there had been any UTF-8 errors,
// we'd have bailed out before now. // we'd have bailed out before now.
let src = unsafe { from_utf8_unchecked(header.src) }; let src = unsafe { from_utf8_unchecked(source) };
let ModuleHeader { let ModuleHeader {
module_id, module_id,

View file

@ -27,6 +27,7 @@ pub enum LowLevel {
ListPrepend, ListPrepend,
ListJoin, ListJoin,
ListMap, ListMap,
ListMap2,
ListMapWithIndex, ListMapWithIndex,
ListKeepIf, ListKeepIf,
ListWalk, ListWalk,

View file

@ -909,6 +909,7 @@ define_builtins! {
21 LIST_KEEP_OKS: "keepOks" 21 LIST_KEEP_OKS: "keepOks"
22 LIST_KEEP_ERRS: "keepErrs" 22 LIST_KEEP_ERRS: "keepErrs"
23 LIST_MAP_WITH_INDEX: "mapWithIndex" 23 LIST_MAP_WITH_INDEX: "mapWithIndex"
24 LIST_MAP2: "map2"
} }
5 RESULT: "Result" => { 5 RESULT: "Result" => {
0 RESULT_RESULT: "Result" imported // the Result.Result type alias 0 RESULT_RESULT: "Result" imported // the Result.Result type alias

View file

@ -12,7 +12,6 @@ roc_module = { path = "../module" }
roc_types = { path = "../types" } roc_types = { path = "../types" }
roc_can = { path = "../can" } roc_can = { path = "../can" }
roc_unify = { path = "../unify" } roc_unify = { path = "../unify" }
roc_constrain = { path = "../constrain" }
roc_solve = { path = "../solve" } roc_solve = { path = "../solve" }
roc_problem = { path = "../problem" } roc_problem = { path = "../problem" }
ven_pretty = { path = "../../vendor/pretty" } ven_pretty = { path = "../../vendor/pretty" }
@ -21,7 +20,6 @@ ven_ena = { path = "../../vendor/ena" }
linked-hash-map = "0.5.4" linked-hash-map = "0.5.4"
[dev-dependencies] [dev-dependencies]
roc_constrain = { path = "../constrain" }
roc_load= { path = "../load" } roc_load= { path = "../load" }
roc_builtins = { path = "../builtins" } roc_builtins = { path = "../builtins" }
roc_parse = { path = "../parse" } roc_parse = { path = "../parse" }

View file

@ -651,6 +651,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
StrJoinWith => arena.alloc_slice_copy(&[borrowed, borrowed]), StrJoinWith => arena.alloc_slice_copy(&[borrowed, borrowed]),
ListJoin => arena.alloc_slice_copy(&[irrelevant]), ListJoin => arena.alloc_slice_copy(&[irrelevant]),
ListMap | ListMapWithIndex => arena.alloc_slice_copy(&[owned, irrelevant]), ListMap | ListMapWithIndex => arena.alloc_slice_copy(&[owned, irrelevant]),
ListMap2 => arena.alloc_slice_copy(&[owned, owned, irrelevant]),
ListKeepIf | ListKeepOks | ListKeepErrs => arena.alloc_slice_copy(&[owned, borrowed]), ListKeepIf | ListKeepOks | ListKeepErrs => arena.alloc_slice_copy(&[owned, borrowed]),
ListContains => arena.alloc_slice_copy(&[borrowed, irrelevant]), ListContains => arena.alloc_slice_copy(&[borrowed, irrelevant]),
ListWalk => arena.alloc_slice_copy(&[owned, irrelevant, owned]), ListWalk => arena.alloc_slice_copy(&[owned, irrelevant, owned]),
@ -659,7 +660,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
// TODO when we have lists with capacity (if ever) // TODO when we have lists with capacity (if ever)
// List.append should own its first argument // List.append should own its first argument
ListAppend => arena.alloc_slice_copy(&[borrowed, owned]), ListAppend => arena.alloc_slice_copy(&[owned, owned]),
Eq | NotEq => arena.alloc_slice_copy(&[borrowed, borrowed]), Eq | NotEq => arena.alloc_slice_copy(&[borrowed, borrowed]),

View file

@ -1,8 +1,8 @@
use self::InProgressProc::*; use self::InProgressProc::*;
use crate::exhaustive::{Ctor, Guard, RenderAs, TagId}; use crate::exhaustive::{Ctor, Guard, RenderAs, TagId};
use crate::layout::{ use crate::layout::{
BuildClosureData, Builtin, ClosureLayout, Layout, LayoutCache, LayoutProblem, UnionLayout, BuildClosureData, Builtin, ClosureLayout, Layout, LayoutCache, LayoutProblem, MemoryMode,
WrappedVariant, TAG_SIZE, UnionLayout, WrappedVariant, TAG_SIZE,
}; };
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use bumpalo::Bump; use bumpalo::Bump;
@ -277,6 +277,7 @@ pub struct Procs<'a> {
pub module_thunks: MutSet<Symbol>, pub module_thunks: MutSet<Symbol>,
pub pending_specializations: Option<MutMap<Symbol, MutMap<Layout<'a>, PendingSpecialization>>>, pub pending_specializations: Option<MutMap<Symbol, MutMap<Layout<'a>, PendingSpecialization>>>,
pub specialized: MutMap<(Symbol, Layout<'a>), InProgressProc<'a>>, pub specialized: MutMap<(Symbol, Layout<'a>), InProgressProc<'a>>,
pub call_by_pointer_wrappers: MutMap<Symbol, Symbol>,
pub runtime_errors: MutMap<Symbol, &'a str>, pub runtime_errors: MutMap<Symbol, &'a str>,
pub externals_others_need: ExternalSpecializations, pub externals_others_need: ExternalSpecializations,
pub externals_we_need: MutMap<ModuleId, ExternalSpecializations>, pub externals_we_need: MutMap<ModuleId, ExternalSpecializations>,
@ -291,6 +292,7 @@ impl<'a> Default for Procs<'a> {
pending_specializations: Some(MutMap::default()), pending_specializations: Some(MutMap::default()),
specialized: MutMap::default(), specialized: MutMap::default(),
runtime_errors: MutMap::default(), runtime_errors: MutMap::default(),
call_by_pointer_wrappers: MutMap::default(),
externals_we_need: MutMap::default(), externals_we_need: MutMap::default(),
externals_others_need: ExternalSpecializations::default(), externals_others_need: ExternalSpecializations::default(),
} }
@ -1741,7 +1743,7 @@ pub fn specialize_all<'a>(
partial_proc, partial_proc,
) { ) {
Ok((proc, layout)) => { Ok((proc, layout)) => {
debug_assert_eq!(outside_layout, layout); debug_assert_eq!(outside_layout, layout, " in {:?}", name);
if let Layout::Closure(args, closure, ret) = layout { if let Layout::Closure(args, closure, ret) = layout {
procs.specialized.remove(&(name, outside_layout)); procs.specialized.remove(&(name, outside_layout));
@ -3355,10 +3357,33 @@ pub fn with_hole<'a>(
} }
} }
List { loc_elems, .. } if loc_elems.is_empty() => { List {
loc_elems,
elem_var,
..
} if loc_elems.is_empty() => {
// because an empty list has an unknown element type, it is handled differently // because an empty list has an unknown element type, it is handled differently
let expr = Expr::EmptyArray; let opt_elem_layout = layout_cache.from_var(env.arena, elem_var, env.subs);
Stmt::Let(assigned, expr, Layout::Builtin(Builtin::EmptyList), hole)
match opt_elem_layout {
Ok(elem_layout) => {
let expr = Expr::EmptyArray;
Stmt::Let(
assigned,
expr,
Layout::Builtin(Builtin::List(
MemoryMode::Refcounted,
env.arena.alloc(elem_layout),
)),
hole,
)
}
Err(LayoutProblem::UnresolvedTypeVar(_)) => {
let expr = Expr::EmptyArray;
Stmt::Let(assigned, expr, Layout::Builtin(Builtin::EmptyList), hole)
}
Err(LayoutProblem::Erroneous) => panic!("list element is error type"),
}
} }
List { List {
@ -3654,18 +3679,27 @@ pub fn with_hole<'a>(
captured_symbols.sort(); captured_symbols.sort();
let captured_symbols = captured_symbols.into_bump_slice(); let captured_symbols = captured_symbols.into_bump_slice();
procs let inserted = procs.insert_anonymous(
.insert_anonymous( env,
env, name,
name, function_type,
function_type, arguments,
arguments, loc_body,
loc_body, CapturedSymbols::Captured(captured_symbols),
CapturedSymbols::Captured(captured_symbols), return_type,
return_type, layout_cache,
layout_cache, );
)
.unwrap(); if let Err(runtime_error) = inserted {
return Stmt::RuntimeError(env.arena.alloc(format!(
"RuntimeError {} line {} {:?}",
file!(),
line!(),
runtime_error,
)));
} else {
drop(inserted);
}
let closure_data_layout = closure_layout.as_block_of_memory_layout(); let closure_data_layout = closure_layout.as_block_of_memory_layout();
// define the function pointer // define the function pointer
@ -4652,27 +4686,11 @@ fn from_can_when<'a>(
} }
let opt_branches = to_opt_branches(env, region, branches, layout_cache); let opt_branches = to_opt_branches(env, region, branches, layout_cache);
let cond_layout = match layout_cache.from_var(env.arena, cond_var, env.subs) { let cond_layout =
Ok(cached) => cached, return_on_layout_error!(env, layout_cache.from_var(env.arena, cond_var, env.subs));
Err(LayoutProblem::UnresolvedTypeVar(_)) => {
return Stmt::RuntimeError(env.arena.alloc(format!(
"UnresolvedTypeVar {} line {}",
file!(),
line!()
)));
}
Err(LayoutProblem::Erroneous) => {
return Stmt::RuntimeError(env.arena.alloc(format!(
"Erroneous {} line {}",
file!(),
line!()
)));
}
};
let ret_layout = layout_cache let ret_layout =
.from_var(env.arena, expr_var, env.subs) return_on_layout_error!(env, layout_cache.from_var(env.arena, expr_var, env.subs));
.unwrap_or_else(|err| panic!("TODO turn this into a RuntimeError {:?}", err));
let arena = env.arena; let arena = env.arena;
let it = opt_branches let it = opt_branches
@ -5733,6 +5751,12 @@ fn call_by_pointer<'a>(
match layout { match layout {
Layout::FunctionPointer(arg_layouts, ret_layout) if !is_thunk => { Layout::FunctionPointer(arg_layouts, ret_layout) if !is_thunk => {
if arg_layouts.iter().any(|l| l.contains_refcounted()) { if arg_layouts.iter().any(|l| l.contains_refcounted()) {
if let Some(wrapper) = procs.call_by_pointer_wrappers.get(&symbol) {
if procs.specialized.contains_key(&(*wrapper, layout.clone())) {
return Expr::FunctionPointer(*wrapper, layout);
}
}
let name = env.unique_symbol(); let name = env.unique_symbol();
let mut args = Vec::with_capacity_in(arg_layouts.len(), env.arena); let mut args = Vec::with_capacity_in(arg_layouts.len(), env.arena);
let mut arg_symbols = Vec::with_capacity_in(arg_layouts.len(), env.arena); let mut arg_symbols = Vec::with_capacity_in(arg_layouts.len(), env.arena);
@ -5777,6 +5801,9 @@ fn call_by_pointer<'a>(
procs procs
.specialized .specialized
.insert((name, layout.clone()), InProgressProc::Done(proc)); .insert((name, layout.clone()), InProgressProc::Done(proc));
procs.call_by_pointer_wrappers.insert(symbol, name);
Expr::FunctionPointer(name, layout) Expr::FunctionPointer(name, layout)
} else { } else {
// if none of the arguments is refcounted, then owning the arguments has no // if none of the arguments is refcounted, then owning the arguments has no
@ -5786,6 +5813,12 @@ fn call_by_pointer<'a>(
} }
Layout::FunctionPointer(arg_layouts, ret_layout) => { Layout::FunctionPointer(arg_layouts, ret_layout) => {
if arg_layouts.iter().any(|l| l.contains_refcounted()) { if arg_layouts.iter().any(|l| l.contains_refcounted()) {
if let Some(wrapper) = procs.call_by_pointer_wrappers.get(&symbol) {
if procs.specialized.contains_key(&(*wrapper, layout.clone())) {
return Expr::FunctionPointer(*wrapper, layout);
}
}
let name = env.unique_symbol(); let name = env.unique_symbol();
let mut args = Vec::with_capacity_in(arg_layouts.len(), env.arena); let mut args = Vec::with_capacity_in(arg_layouts.len(), env.arena);
let mut arg_symbols = Vec::with_capacity_in(arg_layouts.len(), env.arena); let mut arg_symbols = Vec::with_capacity_in(arg_layouts.len(), env.arena);
@ -5834,6 +5867,9 @@ fn call_by_pointer<'a>(
procs procs
.specialized .specialized
.insert((name, layout.clone()), InProgressProc::Done(proc)); .insert((name, layout.clone()), InProgressProc::Done(proc));
procs.call_by_pointer_wrappers.insert(symbol, name);
Expr::FunctionPointer(name, layout) Expr::FunctionPointer(name, layout)
} else { } else {
// if none of the arguments is refcounted, then owning the arguments has no // if none of the arguments is refcounted, then owning the arguments has no
@ -6201,8 +6237,7 @@ fn call_by_name<'a>(
procs.runtime_errors.insert(proc_name, error_msg); procs.runtime_errors.insert(proc_name, error_msg);
panic!(); Stmt::RuntimeError(error_msg)
// Stmt::RuntimeError(error_msg)
} }
} }
} }

View file

@ -683,7 +683,6 @@ mod test_mono {
let Test.9 = 2i64; let Test.9 = 2i64;
let Test.4 = Array [Test.8, Test.9]; let Test.4 = Array [Test.8, Test.9];
let Test.3 = CallByName Test.1 Test.4; let Test.3 = CallByName Test.1 Test.4;
dec Test.4;
ret Test.3; ret Test.3;
"# "#
), ),
@ -709,7 +708,6 @@ mod test_mono {
let Test.2 = Array [Test.5]; let Test.2 = Array [Test.5];
let Test.3 = 2i64; let Test.3 = 2i64;
let Test.1 = CallByName List.5 Test.2 Test.3; let Test.1 = CallByName List.5 Test.2 Test.3;
dec Test.2;
ret Test.1; ret Test.1;
"# "#
), ),

View file

@ -246,7 +246,7 @@ fn to_expr_report<'a>(
} }
} }
EExpr::Start(row, col) => { EExpr::Start(row, col) | EExpr::IndentStart(row, col) => {
let (context_row, context_col, a_thing) = match context { let (context_row, context_col, a_thing) = match context {
Context::InNode(node, r, c, _) => match node { Context::InNode(node, r, c, _) => match node {
Node::WhenCondition | Node::WhenBranch | Node::WhenIfGuard => ( Node::WhenCondition | Node::WhenBranch | Node::WhenIfGuard => (

View file

@ -146,12 +146,12 @@ pub const DEFAULT_PALETTE: Palette = Palette {
}; };
pub const RED_CODE: &str = "\u{001b}[31m"; pub const RED_CODE: &str = "\u{001b}[31m";
pub const WHITE_CODE: &str = "\u{001b}[37m"; pub const GREEN_CODE: &str = "\u{001b}[32m";
pub const BLUE_CODE: &str = "\u{001b}[34m";
pub const YELLOW_CODE: &str = "\u{001b}[33m"; pub const YELLOW_CODE: &str = "\u{001b}[33m";
pub const GREEN_CODE: &str = "\u{001b}[42m"; pub const BLUE_CODE: &str = "\u{001b}[34m";
pub const CYAN_CODE: &str = "\u{001b}[36m";
pub const MAGENTA_CODE: &str = "\u{001b}[35m"; pub const MAGENTA_CODE: &str = "\u{001b}[35m";
pub const CYAN_CODE: &str = "\u{001b}[36m";
pub const WHITE_CODE: &str = "\u{001b}[37m";
pub const BOLD_CODE: &str = "\u{001b}[1m"; pub const BOLD_CODE: &str = "\u{001b}[1m";

View file

@ -5572,4 +5572,38 @@ mod test_reporting {
), ),
) )
} }
#[test]
#[ignore]
fn double_binop() {
report_problem_as(
indoc!(
r#"
key >= 97 && <= 122
"#
),
indoc!(
r#"
"#
),
)
}
#[test]
#[ignore]
fn case_of() {
report_problem_as(
indoc!(
r#"
case 1 of
1 -> True
_ -> False
"#
),
indoc!(
r#"
"#
),
)
}
} }

View file

@ -12,11 +12,8 @@ roc_module = { path = "../module" }
roc_types = { path = "../types" } roc_types = { path = "../types" }
roc_can = { path = "../can" } roc_can = { path = "../can" }
roc_unify = { path = "../unify" } roc_unify = { path = "../unify" }
roc_constrain = { path = "../constrain" }
roc_builtins = { path = "../builtins" }
[dev-dependencies] [dev-dependencies]
roc_constrain = { path = "../constrain" }
roc_load = { path = "../load" } roc_load = { path = "../load" }
roc_builtins = { path = "../builtins" } roc_builtins = { path = "../builtins" }
roc_problem = { path = "../problem" } roc_problem = { path = "../problem" }

View file

@ -29,7 +29,6 @@ bumpalo = { version = "3.2", features = ["collections"] }
inlinable_string = "0.1" inlinable_string = "0.1"
either = "1.6.1" either = "1.6.1"
indoc = "0.3.3" indoc = "0.3.3"
pretty_assertions = "0.5.1"
libc = "0.2" libc = "0.2"
# NOTE: rtfeldman/inkwell is a fork of TheDan64/inkwell which does not change anything. # NOTE: rtfeldman/inkwell is a fork of TheDan64/inkwell which does not change anything.
# #

View file

@ -568,6 +568,35 @@ fn list_map_closure() {
); );
} }
#[test]
fn list_map2_pair() {
assert_evals_to!(
indoc!(
r#"
List.map2 [1,2,3] [3,2,1] (\a,b -> Pair a b)
"#
),
RocList::from_slice(&[(1, 3), (2, 2), (3, 1)]),
RocList<(i64, i64)>
);
}
#[test]
fn list_map2_different_lengths() {
assert_evals_to!(
indoc!(
r#"
List.map2
["a", "b", "lllllllllllllongnggg" ]
["b"]
Str.concat
"#
),
RocList::from_slice(&[RocStr::from_slice("ab".as_bytes()),]),
RocList<RocStr>
);
}
#[test] #[test]
fn list_join_empty_list() { fn list_join_empty_list() {
assert_evals_to!("List.join []", RocList::from_slice(&[]), RocList<i64>); assert_evals_to!("List.join []", RocList::from_slice(&[]), RocList<i64>);

File diff suppressed because it is too large Load diff

View file

@ -9,8 +9,6 @@ license = "Apache-2.0"
roc_collections = { path = "../collections" } roc_collections = { path = "../collections" }
roc_region = { path = "../region" } roc_region = { path = "../region" }
roc_module = { path = "../module" } roc_module = { path = "../module" }
roc_parse = { path = "../parse" }
roc_problem = { path = "../problem" }
ven_ena = { path = "../../vendor/ena" } ven_ena = { path = "../../vendor/ena" }
inlinable_string = "0.1" inlinable_string = "0.1"

View file

@ -371,7 +371,10 @@ impl SolvedType {
match subs.get_without_compacting(var).content { match subs.get_without_compacting(var).content {
FlexVar(_) => SolvedType::Flex(VarId::from_var(var, subs)), FlexVar(_) => SolvedType::Flex(VarId::from_var(var, subs)),
RecursionVar { .. } => SolvedType::Flex(VarId::from_var(var, subs)), RecursionVar { structure, .. } => {
// TODO should there be a SolvedType RecursionVar variant?
Self::from_var_help(subs, recursion_vars, structure)
}
RigidVar(name) => SolvedType::Rigid(name), RigidVar(name) => SolvedType::Rigid(name),
Structure(flat_type) => Self::from_flat_type(subs, recursion_vars, flat_type), Structure(flat_type) => Self::from_flat_type(subs, recursion_vars, flat_type),
Alias(symbol, args, actual_var) => { Alias(symbol, args, actual_var) => {

View file

@ -867,7 +867,11 @@ fn unify_shared_tags(
merge(subs, ctx, Structure(flat_type)) merge(subs, ctx, Structure(flat_type))
} else { } else {
mismatch!("Problem with Tag Union") mismatch!(
"Problem with Tag Union\nThere should be {:?} matching tags, but I only got \n{:?}",
num_shared_tags,
&matching_tags
)
} }
} }

View file

@ -15,16 +15,8 @@ roc_region = { path = "../compiler/region" }
roc_module = { path = "../compiler/module" } roc_module = { path = "../compiler/module" }
roc_problem = { path = "../compiler/problem" } roc_problem = { path = "../compiler/problem" }
roc_types = { path = "../compiler/types" } roc_types = { path = "../compiler/types" }
roc_builtins = { path = "../compiler/builtins" }
roc_constrain = { path = "../compiler/constrain" }
roc_unify = { path = "../compiler/unify" }
roc_solve = { path = "../compiler/solve" }
roc_mono = { path = "../compiler/mono" }
roc_load = { path = "../compiler/load" }
roc_gen = { path = "../compiler/gen" }
roc_fmt = { path = "../compiler/fmt" } roc_fmt = { path = "../compiler/fmt" }
roc_reporting = { path = "../compiler/reporting" }
# TODO switch to clap 3.0.0 once it's out. Tried adding clap = "~3.0.0-beta.1" and cargo wouldn't accept it # TODO switch to clap 3.0.0 once it's out. Tried adding clap = "~3.0.0-beta.1" and cargo wouldn't accept it
ven_graph = { path = "../vendor/pathfinding" } ven_graph = { path = "../vendor/pathfinding" }
im = "14" # im and im-rc should always have the same version! im = "14" # im and im-rc should always have the same version!
@ -34,25 +26,6 @@ inlinable_string = "0.1"
arraystring = "0.3.0" arraystring = "0.3.0"
libc = "0.2" libc = "0.2"
page_size = "0.4" page_size = "0.4"
# NOTE: rtfeldman/inkwell is a fork of TheDan64/inkwell which does not change anything.
#
# The reason for this fork is that the way Inkwell is designed, you have to use
# a particular branch (e.g. "llvm8-0") in Cargo.toml. That would be fine, except that
# breaking changes get pushed directly to that branch, which breaks our build
# without warning.
#
# We tried referencing a specific rev on TheDan64/inkwell directly (instead of branch),
# but although that worked locally, it did not work on GitHub Actions. (After a few
# hours of investigation, gave up trying to figure out why.) So this is the workaround:
# having an immutable tag on the rtfeldman/inkwell fork which points to
# a particular "release" of Inkwell.
#
# When we want to update Inkwell, we can sync up rtfeldman/inkwell to the latest
# commit of TheDan64/inkwell, push a new tag which points to the latest commit,
# change the tag value in this Cargo.toml to point to that tag, and `cargo update`.
# This way, GitHub Actions works and nobody's builds get broken.
inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release3" }
target-lexicon = "0.10"
winit = "0.22" winit = "0.22"
wgpu = "0.6" wgpu = "0.6"
glyph_brush = "0.7" glyph_brush = "0.7"
@ -62,7 +35,6 @@ env_logger = "0.7"
futures = "0.3" futures = "0.3"
wgpu_glyph = "0.10" wgpu_glyph = "0.10"
cgmath = "0.17.0" cgmath = "0.17.0"
itertools = "0.9.0"
snafu = { version = "0.6", features = ["backtraces"] } snafu = { version = "0.6", features = ["backtraces"] }
colored = "2" colored = "2"
pest = "2.1" pest = "2.1"

View file

@ -1,141 +1,28 @@
interface Base64 exposes [ fromBytes ] imports [ Bytes.Decode ] interface Base64 exposes [ fromBytes, fromStr, toBytes, toStr ] imports [ Base64.Decode, Base64.Encode ]
Decoder a : Bytes.Decode.Decoder a # base 64 encoding from a sequence of bytes
fromBytes : List U8 -> Result Str [ InvalidInput ]*
fromBytes : List U8 -> Result Str Bytes.Decode.DecodeError
fromBytes = \bytes -> fromBytes = \bytes ->
Bytes.Decode.decode bytes (decodeBase64 (List.len bytes)) when Base64.Decode.fromBytes bytes is
Ok v -> Ok v
Err _ -> Err InvalidInput
# base 64 encoding from a string
fromStr : Str -> Result Str [ InvalidInput ]*
fromStr = \str ->
fromBytes (Str.toBytes str)
decodeBase64 : Nat -> Bytes.Decode.Decoder Str # base64-encode bytes to the original
decodeBase64 = \width -> Bytes.Decode.loop loopHelp { remaining: width, string: "" } toBytes : Str -> Result (List U8) [ InvalidInput ]*
toBytes = \str ->
Ok (Base64.Encode.toBytes str)
loopHelp : { remaining : Nat, string : Str } -> Decoder (Bytes.Decode.Step { remaining : Nat, string : Str } Str) toStr : Str -> Result Str [ InvalidInput ]*
loopHelp = \{ remaining, string } -> toStr = \str ->
if remaining >= 3 then when toBytes str is
Bytes.Decode.map3 Ok bytes ->
Bytes.Decode.u8 when Str.fromUtf8 bytes is
Bytes.Decode.u8 Ok v -> Ok v
Bytes.Decode.u8 Err _ -> Err InvalidInput
\x, y, z ->
a : U32
a = Num.intCast x
b : U32
b = Num.intCast y
c : U32
c = Num.intCast z
combined = Num.bitwiseOr (Num.bitwiseOr (Num.shiftLeftBy 16 a) (Num.shiftLeftBy 8 b)) c Err _ -> Err InvalidInput
Loop
{
remaining: remaining - 3,
string: Str.concat string (bitsToChars combined 0)
}
else if remaining == 0 then
Bytes.Decode.succeed (Done string)
else if remaining == 2 then
Bytes.Decode.map2
Bytes.Decode.u8
Bytes.Decode.u8
\x, y ->
a : U32
a = Num.intCast x
b : U32
b = Num.intCast y
combined = Num.bitwiseOr (Num.shiftLeftBy 16 a) (Num.shiftLeftBy 8 b)
Done (Str.concat string (bitsToChars combined 1))
else
# remaining = 1
Bytes.Decode.map
Bytes.Decode.u8
\x ->
a : U32
a = Num.intCast x
Done (Str.concat string (bitsToChars (Num.shiftLeftBy 16 a) 2))
bitsToChars : U32, Int * -> Str
bitsToChars = \bits, missing ->
when Str.fromUtf8 (bitsToCharsHelp bits missing) is
Ok str -> str
Err _ -> ""
# Mask that can be used to get the lowest 6 bits of a binary number
lowest6BitsMask : Int *
lowest6BitsMask = 63
bitsToCharsHelp : U32, Int * -> List U8
bitsToCharsHelp = \bits, missing ->
# The input is 24 bits, which we have to partition into 4 6-bit segments. We achieve this by
# shifting to the right by (a multiple of) 6 to remove unwanted bits on the right, then `Num.bitwiseAnd`
# with `0b111111` (which is 2^6 - 1 or 63) (so, 6 1s) to remove unwanted bits on the left.
# any 6-bit number is a valid base64 digit, so this is actually safe
p =
Num.shiftRightZfBy 18 bits
|> Num.intCast
|> unsafeToChar
q =
Num.bitwiseAnd (Num.shiftRightZfBy 12 bits) lowest6BitsMask
|> Num.intCast
|> unsafeToChar
r =
Num.bitwiseAnd (Num.shiftRightZfBy 6 bits) lowest6BitsMask
|> Num.intCast
|> unsafeToChar
s =
Num.bitwiseAnd bits lowest6BitsMask
|> Num.intCast
|> unsafeToChar
equals : U8
equals = 61
when missing is
0 ->
[ p, q, r, s ]
1 ->
[ p, q, r, equals ]
2 ->
[ p, q, equals , equals ]
_ ->
# unreachable
[]
# Base64 index to character/digit
unsafeToChar : U8 -> U8
unsafeToChar = \n ->
if n <= 25 then
# uppercase characters
65 + n
else if n <= 51 then
# lowercase characters
97 + (n - 26)
else if n <= 61 then
# digit characters
48 + (n - 52)
else
# special cases
when n is
62 ->
# '+'
43
63 ->
# '/'
47
_ ->
# anything else is invalid '\u{0000}'
0

View file

@ -0,0 +1,137 @@
interface Base64.Decode exposes [ fromBytes ] imports [ Bytes.Decode.{ Decoder, DecodeProblem } ]
fromBytes : List U8 -> Result Str DecodeProblem
fromBytes = \bytes ->
Bytes.Decode.decode bytes (decodeBase64 (List.len bytes))
decodeBase64 : Nat -> Decoder Str
decodeBase64 = \width -> Bytes.Decode.loop loopHelp { remaining: width, string: "" }
loopHelp : { remaining : Nat, string : Str } -> Decoder (Bytes.Decode.Step { remaining : Nat, string : Str } Str)
loopHelp = \{ remaining, string } ->
if remaining >= 3 then
Bytes.Decode.map3
Bytes.Decode.u8
Bytes.Decode.u8
Bytes.Decode.u8
\x, y, z ->
a : U32
a = Num.intCast x
b : U32
b = Num.intCast y
c : U32
c = Num.intCast z
combined = Num.bitwiseOr (Num.bitwiseOr (Num.shiftLeftBy 16 a) (Num.shiftLeftBy 8 b)) c
Loop
{
remaining: remaining - 3,
string: Str.concat string (bitsToChars combined 0)
}
else if remaining == 0 then
Bytes.Decode.succeed (Done string)
else if remaining == 2 then
Bytes.Decode.map2
Bytes.Decode.u8
Bytes.Decode.u8
\x, y ->
a : U32
a = Num.intCast x
b : U32
b = Num.intCast y
combined = Num.bitwiseOr (Num.shiftLeftBy 16 a) (Num.shiftLeftBy 8 b)
Done (Str.concat string (bitsToChars combined 1))
else
# remaining = 1
Bytes.Decode.map
Bytes.Decode.u8
\x ->
a : U32
a = Num.intCast x
Done (Str.concat string (bitsToChars (Num.shiftLeftBy 16 a) 2))
bitsToChars : U32, Int * -> Str
bitsToChars = \bits, missing ->
when Str.fromUtf8 (bitsToCharsHelp bits missing) is
Ok str -> str
Err _ -> ""
# Mask that can be used to get the lowest 6 bits of a binary number
lowest6BitsMask : Int *
lowest6BitsMask = 63
bitsToCharsHelp : U32, Int * -> List U8
bitsToCharsHelp = \bits, missing ->
# The input is 24 bits, which we have to partition into 4 6-bit segments. We achieve this by
# shifting to the right by (a multiple of) 6 to remove unwanted bits on the right, then `Num.bitwiseAnd`
# with `0b111111` (which is 2^6 - 1 or 63) (so, 6 1s) to remove unwanted bits on the left.
# any 6-bit number is a valid base64 digit, so this is actually safe
p =
Num.shiftRightZfBy 18 bits
|> Num.intCast
|> unsafeToChar
q =
Num.bitwiseAnd (Num.shiftRightZfBy 12 bits) lowest6BitsMask
|> Num.intCast
|> unsafeToChar
r =
Num.bitwiseAnd (Num.shiftRightZfBy 6 bits) lowest6BitsMask
|> Num.intCast
|> unsafeToChar
s =
Num.bitwiseAnd bits lowest6BitsMask
|> Num.intCast
|> unsafeToChar
equals : U8
equals = 61
when missing is
0 ->
[ p, q, r, s ]
1 ->
[ p, q, r, equals ]
2 ->
[ p, q, equals , equals ]
_ ->
# unreachable
[]
# Base64 index to character/digit
unsafeToChar : U8 -> U8
unsafeToChar = \n ->
if n <= 25 then
# uppercase characters
65 + n
else if n <= 51 then
# lowercase characters
97 + (n - 26)
else if n <= 61 then
# digit characters
48 + (n - 52)
else
# special cases
when n is
62 ->
# '+'
43
63 ->
# '/'
47
_ ->
# anything else is invalid '\u{0000}'
0

View file

@ -0,0 +1,195 @@
interface Base64.Encode
exposes [ toBytes ]
imports [ Bytes.Encode.{ Encoder } ]
InvalidChar : U8
# State : [ None, One U8, Two U8, Three U8 ]
toBytes : Str -> List U8
toBytes = \str ->
str
|> Str.toBytes
|> encodeChunks
|> Bytes.Encode.sequence
|> Bytes.Encode.encode
encodeChunks : List U8 -> List Encoder
encodeChunks = \bytes ->
List.walk bytes folder { output: [], accum: None }
|> encodeResidual
coerce : Nat, a -> a
coerce = \_, x -> x
# folder : U8, { output : List Encoder, accum : State } -> { output : List Encoder, accum : State }
folder = \char, { output, accum } ->
when accum is
Unreachable n -> coerce n { output, accum: Unreachable n }
None -> { output, accum: One char }
One a -> { output, accum: Two a char }
Two a b -> { output, accum: Three a b char }
Three a b c ->
when encodeCharacters a b c char is
Ok encoder ->
{
output: List.append output encoder,
accum: None
}
Err _ ->
{ output, accum: None }
# SGVs bG8g V29y bGQ=
# encodeResidual : { output : List Encoder, accum : State } -> List Encoder
encodeResidual = \{ output, accum } ->
when accum is
Unreachable _ -> output
None -> output
One _ -> output
Two a b ->
when encodeCharacters a b equals equals is
Ok encoder ->
List.append output encoder
Err _ ->
output
Three a b c ->
when encodeCharacters a b c equals is
Ok encoder ->
List.append output encoder
Err _ ->
output
equals : U8
equals = 61
# Convert 4 characters to 24 bits (as an Encoder)
encodeCharacters : U8, U8, U8, U8 -> Result Encoder InvalidChar
encodeCharacters = \a,b,c,d ->
if !(isValidChar a) then
Err a
else if !(isValidChar b) then
Err b
else
# `=` is the padding character, and must be special-cased
# only the `c` and `d` char are allowed to be padding
n1 = unsafeConvertChar a
n2 = unsafeConvertChar b
x : U32
x = Num.intCast n1
y : U32
y = Num.intCast n2
if d == equals then
if c == equals then
n = Num.bitwiseOr (Num.shiftLeftBy 18 x) (Num.shiftLeftBy 12 y)
# masking higher bits is not needed, Encode.unsignedInt8 ignores higher bits
b1 : U8
b1 = Num.intCast (Num.shiftRightBy 16 n)
Ok (Bytes.Encode.u8 b1)
else if !(isValidChar c) then
Err c
else
n3 = unsafeConvertChar c
z : U32
z = Num.intCast n3
n = Num.bitwiseOr (Num.bitwiseOr (Num.shiftLeftBy 18 x) (Num.shiftLeftBy 12 y)) (Num.shiftLeftBy 6 z)
combined : U16
combined = Num.intCast (Num.shiftRightBy 8 n)
Ok (Bytes.Encode.u16 BE combined)
else if !(isValidChar d) then
Err d
else
n3 = unsafeConvertChar c
n4 = unsafeConvertChar d
z : U32
z = Num.intCast n3
w : U32
w = Num.intCast n4
n =
Num.bitwiseOr
(Num.bitwiseOr (Num.shiftLeftBy 18 x) (Num.shiftLeftBy 12 y))
(Num.bitwiseOr (Num.shiftLeftBy 6 z) w)
b3 : U8
b3 = Num.intCast n
combined : U16
combined = Num.intCast (Num.shiftRightBy 8 n)
Ok (Bytes.Encode.sequence [ Bytes.Encode.u16 BE combined, Bytes.Encode.u8 b3 ])
# is the character a base64 digit?
# The base16 digits are: A-Z, a-z, 0-1, '+' and '/'
isValidChar : U8 -> Bool
isValidChar = \c ->
if isAlphaNum c then
True
else
when c is
43 ->
# '+'
True
47 ->
# '/'
True
_ ->
False
isAlphaNum : U8 -> Bool
isAlphaNum = \key ->
(key >= 48 && key <= 57) || (key >= 64 && key <= 90) || (key >= 97 && key <= 122)
# Convert a base64 character/digit to its index
# See also [Wikipedia](https://en.wikipedia.org/wiki/Base64#Base64_table)
unsafeConvertChar : U8 -> U8
unsafeConvertChar = \key ->
if key >= 65 && key <= 90 then
# A-Z
key - 65
else if key >= 97 && key <= 122 then
# a-z
(key - 97) + 26
else if key >= 48 && key <= 57 then
# 0-9
(key - 48) + 26 + 26
else
when key is
43 ->
# '+'
62
47 ->
# '/'
63
_ ->
0

View file

@ -1,13 +1,13 @@
interface Bytes.Decode exposes [ Decoder, decode, map, map2, u8, loop, Step, succeed, DecodeError, after, map3 ] imports [] interface Bytes.Decode exposes [ Decoder, decode, map, map2, u8, loop, Step, succeed, DecodeProblem, after, map3 ] imports []
State : { bytes: List U8, cursor : Nat } State : { bytes: List U8, cursor : Nat }
DecodeError : [ OutOfBytes ] DecodeProblem : [ OutOfBytes ]
Decoder a : [ @Decoder (State -> [Good State a, Bad DecodeError]) ] Decoder a : [ @Decoder (State -> [Good State a, Bad DecodeProblem]) ]
decode : List U8, Decoder a -> Result a DecodeError decode : List U8, Decoder a -> Result a DecodeProblem
decode = \bytes, @Decoder decoder -> decode = \bytes, @Decoder decoder ->
when decoder { bytes, cursor: 0 } is when decoder { bytes, cursor: 0 } is
Good _ value -> Good _ value ->

View file

@ -0,0 +1,124 @@
interface Bytes.Encode exposes [ Encoder, sequence, u8, u16, bytes, empty, encode ] imports []
Endianness : [ BE, LE ]
Encoder : [ Signed8 I8, Unsigned8 U8, Signed16 Endianness I16, Unsigned16 Endianness U16, Sequence Nat (List Encoder), Bytes (List U8) ]
u8 : U8 -> Encoder
u8 = \value -> Unsigned8 value
empty : Encoder
empty =
foo : List Encoder
foo = []
Sequence 0 foo
u16 : Endianness, U16 -> Encoder
u16 = \endianness, value -> Unsigned16 endianness value
bytes : List U8 -> Encoder
bytes = \bs -> Bytes bs
sequence : List Encoder -> Encoder
sequence = \encoders ->
Sequence (getWidths encoders 0) encoders
getWidth : Encoder -> Nat
getWidth = \encoder ->
when encoder is
Signed8 _ -> 1
Unsigned8 _ -> 1
Signed16 _ _ -> 2
Unsigned16 _ _ -> 2
# Signed32 _ -> 4
# Unsigned32 _ -> 4
# Signed64 _ -> 8
# Unsigned64 _ -> 8
# Signed128 _ -> 16
# Unsigned128 _ -> 16
Sequence w _ -> w
Bytes bs -> List.len bs
getWidths : List Encoder, Nat -> Nat
getWidths = \encoders, initial -> List.walk encoders (\encoder, accum -> accum + getWidth encoder) initial
encode : Encoder -> List U8
encode = \encoder ->
output = List.repeat (getWidth encoder) 0
encodeHelp encoder 0 output
|> .output
encodeHelp = \encoder, offset, output ->
when encoder is
Unsigned8 value ->
{
output: List.set output offset value,
offset: offset + 1
}
Signed8 value ->
cast : U8
cast = Num.intCast value
{
output: List.set output offset cast,
offset: offset + 1
}
Unsigned16 endianness value ->
a : U8
a = Num.intCast (Num.shiftRightBy 8 value)
b : U8
b = Num.intCast value
newOutput =
when endianness is
BE ->
output
|> List.set (offset + 0) a
|> List.set (offset + 1) b
LE ->
output
|> List.set (offset + 0) b
|> List.set (offset + 1) a
{
output: newOutput,
offset: offset + 2
}
Signed16 endianness value ->
a : U8
a = Num.intCast (Num.shiftRightBy 8 value)
b : U8
b = Num.intCast value
newOutput =
when endianness is
BE ->
output
|> List.set (offset + 0) a
|> List.set (offset + 1) b
LE ->
output
|> List.set (offset + 0) b
|> List.set (offset + 1) a
{
output: newOutput,
offset: offset + 1
}
Bytes bs ->
List.walk bs (\byte, accum -> { offset: accum.offset + 1, output : List.set accum.output offset byte }) { output, offset }
Sequence _ encoders ->
List.walk encoders (\single, accum -> encodeHelp single accum.offset accum.output) { output, offset }

View file

@ -8,8 +8,8 @@ app "closure"
main : Task.Task {} [] main : Task.Task {} []
main = closure1 {} main = closure1 {}
|> Task.after (\_ -> closure2 {}) |> Task.after (\_ -> closure2 {})
|> Task.after (\_ -> closure2 {}) |> Task.after (\_ -> closure3 {})
|> Task.after (\_ -> closure2 {}) |> Task.after (\_ -> closure4 {})
# --- # ---

View file

@ -8,9 +8,15 @@ IO a : Task.Task a []
main : IO {} main : IO {}
main = main =
when Base64.fromBytes (Str.toBytes "Hello World") is when Base64.fromBytes (Str.toBytes "Hello World") is
Ok str ->
Task.putLine str
Err _ -> Err _ ->
Task.putLine "sadness" Task.putLine "sadness"
Ok encoded ->
Task.after (Task.putLine (Str.concat "encoded: " encoded)) \_ ->
when Base64.toStr encoded is
Ok decoded ->
Task.putLine (Str.concat "decoded: " decoded)
Err _ ->
Task.putLine "sadness"