Merge remote-tracking branch 'origin/trunk' into cli-run-non-app

This commit is contained in:
Folkert 2021-02-24 23:25:26 +01:00
commit da089af4b9
84 changed files with 5676 additions and 2877 deletions

View file

@ -31,8 +31,10 @@ members = [
# Optimizations based on https://deterministic.space/high-performance-rust.html
[profile.release]
lto = "fat"
lto = "thin"
codegen-units = 1
# debug = true # enable when profiling
[profile.bench]
lto = "thin"
codegen-units = 1

View file

@ -24,8 +24,7 @@ install-zig-llvm-valgrind-clippy-rustfmt:
RUN ln -s /usr/bin/clang-10 /usr/bin/clang
# use lld as linker
RUN ln -s /usr/bin/lld-10 /usr/bin/ld.lld
RUN echo "[build]" > $CARGO_HOME/config.toml
RUN echo "rustflags = [\"-C\", \"link-arg=-fuse-ld=lld\", \"-C\", \"target-cpu=native\"]" >> $CARGO_HOME/config.tom
ENV RUSTFLAGS="-C link-arg=-fuse-ld=lld -C target-cpu=native"
# valgrind
RUN apt -y install autotools-dev cmake automake libc6-dbg
RUN wget https://sourceware.org/pub/valgrind/valgrind-3.16.1.tar.bz2
@ -43,6 +42,9 @@ install-zig-llvm-valgrind-clippy-rustfmt:
RUN apt -y install libssl-dev
RUN cargo install sccache
RUN sccache -V
ENV RUSTC_WRAPPER=/usr/local/cargo/bin/sccache
ENV SCCACHE_DIR=/earthbuild/sccache_dir
ENV CARGO_INCREMENTAL=0 # no need to recompile package when using new function
deps-image:
FROM +install-zig-llvm-valgrind-clippy-rustfmt
@ -50,8 +52,6 @@ deps-image:
copy-dirs-and-cache:
FROM +install-zig-llvm-valgrind-clippy-rustfmt
# cache
COPY --dir sccache_dir ./
# roc dirs
COPY --dir cli compiler docs editor roc_std vendor examples Cargo.toml Cargo.lock ./
@ -62,36 +62,31 @@ test-zig:
build-rust:
FROM +copy-dirs-and-cache
ARG RUSTC_WRAPPER=/usr/local/cargo/bin/sccache
ARG SCCACHE_DIR=/earthbuild/sccache_dir
ARG CARGO_INCREMENTAL=0 # no need to recompile package when using new function
RUN cargo build; sccache --show-stats # for clippy
RUN cargo test --release --no-run; sccache --show-stats
RUN --mount=type=cache,target=$SCCACHE_DIR \
cargo build; sccache --show-stats # for clippy
RUN --mount=type=cache,target=$SCCACHE_DIR \
cargo test --release --no-run; sccache --show-stats
check-clippy:
FROM +build-rust
RUN cargo clippy -V
RUN cargo clippy -- -D warnings
RUN --mount=type=cache,target=$SCCACHE_DIR \
cargo clippy -- -D warnings
check-rustfmt:
FROM +copy-dirs-and-cache
RUN cargo fmt --version
RUN cargo fmt --all -- --check
save-cache:
FROM +build-rust
SAVE ARTIFACT sccache_dir AS LOCAL sccache_dir
test-rust:
FROM +build-rust
ARG RUSTC_WRAPPER=/usr/local/cargo/bin/sccache
ARG SCCACHE_DIR=/earthbuild/sccache_dir
RUN cargo test --release
ENV RUST_BACKTRACE=1
RUN --mount=type=cache,target=$SCCACHE_DIR \
cargo test --release
test-all:
BUILD +check-clippy
BUILD +check-rustfmt
BUILD +save-cache
BUILD +test-zig
BUILD +check-rustfmt
BUILD +check-clippy
BUILD +test-rust

View file

@ -199,7 +199,7 @@ mod cli_run {
"deriv",
&[],
"1 count: 6\n2 count: 22\n",
false,
true,
);
}
@ -228,7 +228,6 @@ mod cli_run {
}
#[test]
#[ignore]
#[serial(astar)]
fn run_astar_optimized_1() {
check_output_with_stdin(
@ -241,52 +240,12 @@ mod cli_run {
);
}
#[ignore]
#[test]
#[serial(closure1)]
fn closure1() {
#[serial(closure)]
fn closure() {
check_output(
&example_file("benchmarks", "Closure1.roc"),
"closure1",
&[],
"",
true,
);
}
#[ignore]
#[test]
#[serial(closure2)]
fn closure2() {
check_output(
&example_file("benchmarks", "Closure2.roc"),
"closure2",
&[],
"",
true,
);
}
#[ignore]
#[test]
#[serial(closure3)]
fn closure3() {
check_output(
&example_file("benchmarks", "Closure3.roc"),
"closure3",
&[],
"",
true,
);
}
#[ignore]
#[test]
#[serial(closure4)]
fn closure4() {
check_output(
&example_file("benchmarks", "Closure4.roc"),
"closure4",
&example_file("benchmarks", "Closure.roc"),
"closure",
&[],
"",
true,

View file

@ -296,6 +296,10 @@ pub const RocDict = extern struct {
}
fn getKey(self: *const RocDict, index: usize, alignment: Alignment, key_width: usize, value_width: usize) Opaque {
if (key_width == 0) {
return null;
}
const offset = blk: {
if (alignment.keyFirst()) {
break :blk (index * key_width);
@ -329,6 +333,10 @@ pub const RocDict = extern struct {
}
fn getValue(self: *const RocDict, index: usize, alignment: Alignment, key_width: usize, value_width: usize) Opaque {
if (value_width == 0) {
return null;
}
const offset = blk: {
if (alignment.keyFirst()) {
break :blk (self.capacity() * key_width) + (index * value_width);
@ -492,6 +500,8 @@ pub fn dictRemove(input: RocDict, alignment: Alignment, key: Opaque, key_width:
if (dict.dict_entries_len == 0) {
const data_bytes = dict.capacity() * slotSize(key_width, value_width);
decref(std.heap.c_allocator, alignment, dict.dict_bytes, data_bytes);
output.* = RocDict.empty();
return;
}
output.* = dict;
@ -660,7 +670,9 @@ pub fn dictUnion(dict1: RocDict, dict2: RocDict, alignment: Alignment, key_width
// we need an extra RC token for the key
inc_key(key);
inc_value(value);
// we know the newly added key is not a duplicate, so the `dec`s are unreachable
const dec_key = doNothing;
const dec_value = doNothing;
@ -702,7 +714,7 @@ pub fn dictIntersection(dict1: RocDict, dict2: RocDict, alignment: Alignment, ke
}
}
pub fn dictDifference(dict1: RocDict, dict2: RocDict, alignment: Alignment, key_width: usize, value_width: usize, hash_fn: HashFn, is_eq: EqFn, dec_key: Inc, dec_value: Inc, output: *RocDict) callconv(.C) void {
pub fn dictDifference(dict1: RocDict, dict2: RocDict, alignment: Alignment, key_width: usize, value_width: usize, hash_fn: HashFn, is_eq: EqFn, dec_key: Dec, dec_value: Dec, output: *RocDict) callconv(.C) void {
output.* = dict1.makeUnique(std.heap.c_allocator, alignment, key_width, value_width);
var i: usize = 0;
@ -748,8 +760,14 @@ pub fn setFromList(list: RocList, alignment: Alignment, key_width: usize, value_
}
const StepperCaller = fn (?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void;
pub fn dictWalk(dict: RocDict, stepper: Opaque, stepper_caller: StepperCaller, accum: Opaque, alignment: Alignment, key_width: usize, value_width: usize, accum_width: usize, output: Opaque) callconv(.C) void {
@memcpy(output orelse unreachable, accum orelse unreachable, accum_width);
pub fn dictWalk(dict: RocDict, stepper: Opaque, stepper_caller: StepperCaller, accum: Opaque, alignment: Alignment, key_width: usize, value_width: usize, accum_width: usize, inc_key: Inc, inc_value: Inc, output: Opaque) callconv(.C) void {
// allocate space to write the result of the stepper into
// experimentally aliasing the accum and output pointers is not a good idea
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);
var i: usize = 0;
const size = dict.capacity();
@ -759,12 +777,19 @@ pub fn dictWalk(dict: RocDict, stepper: Opaque, stepper_caller: StepperCaller, a
const key = dict.getKey(i, alignment, key_width, value_width);
const value = dict.getValue(i, alignment, key_width, value_width);
stepper_caller(stepper, key, value, output, output);
stepper_caller(stepper, key, value, b2, b1);
const temp = b1;
b2 = b1;
b1 = temp;
},
else => {},
}
}
@memcpy(output orelse unreachable, b2, accum_width);
std.heap.c_allocator.free(alloc[0..accum_width]);
const data_bytes = dict.capacity() * slotSize(key_width, value_width);
decref(std.heap.c_allocator, alignment, dict.dict_bytes, data_bytes);
}

View file

@ -7,6 +7,9 @@ const Allocator = mem.Allocator;
const EqFn = fn (?[*]u8, ?[*]u8) callconv(.C) bool;
const Opaque = ?[*]u8;
const Inc = fn (?[*]u8) callconv(.C) void;
const Dec = fn (?[*]u8) callconv(.C) void;
pub const RocList = extern struct {
bytes: ?[*]u8,
length: usize,
@ -58,7 +61,7 @@ pub const RocList = extern struct {
}
// unfortunately, we have to clone
var new_list = RocList.allocate(allocator, self.length, alignment, element_width);
var new_list = RocList.allocate(allocator, alignment, self.length, element_width);
var old_bytes: [*]u8 = @ptrCast([*]u8, self.bytes);
var new_bytes: [*]u8 = @ptrCast([*]u8, new_list.bytes);
@ -149,7 +152,7 @@ pub fn listMapWithIndex(list: RocList, transform: Opaque, caller: Caller2, align
}
}
pub fn listKeepIf(list: RocList, transform: Opaque, caller: Caller1, alignment: usize, element_width: usize) 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| {
const size = list.len();
var i: usize = 0;
@ -160,6 +163,7 @@ pub fn listKeepIf(list: RocList, transform: Opaque, caller: Caller1, alignment:
while (i < size) : (i += 1) {
var keep = false;
const element = source_ptr + (i * element_width);
inc(element);
caller(transform, element, @ptrCast(?[*]u8, &keep));
if (keep) {
@ -167,29 +171,36 @@ pub fn listKeepIf(list: RocList, transform: Opaque, caller: Caller1, alignment:
kept += 1;
} else {
// TODO decrement the value?
dec(element);
}
}
output.length = kept;
// consume the input list
utils.decref(std.heap.c_allocator, alignment, list.bytes, size * element_width);
return output;
if (kept == 0) {
// if the output is empty, deallocate the space we made for the result
utils.decref(std.heap.c_allocator, alignment, output.bytes, size * element_width);
return RocList.empty();
} else {
output.length = kept;
return output;
}
} else {
return RocList.empty();
}
}
pub fn listKeepOks(list: RocList, transform: Opaque, caller: Caller1, alignment: usize, before_width: usize, result_width: usize, after_width: usize) callconv(.C) RocList {
return listKeepResult(list, RocResult.isOk, transform, caller, alignment, before_width, result_width, after_width);
pub fn listKeepOks(list: RocList, transform: Opaque, caller: Caller1, alignment: usize, before_width: usize, result_width: usize, after_width: usize, inc_closure: Inc, dec_result: Dec) callconv(.C) RocList {
return listKeepResult(list, RocResult.isOk, transform, caller, alignment, before_width, result_width, after_width, inc_closure, dec_result);
}
pub fn listKeepErrs(list: RocList, transform: Opaque, caller: Caller1, alignment: usize, before_width: usize, result_width: usize, after_width: usize) callconv(.C) RocList {
return listKeepResult(list, RocResult.isErr, transform, caller, alignment, before_width, result_width, after_width);
pub fn listKeepErrs(list: RocList, transform: Opaque, caller: Caller1, alignment: usize, before_width: usize, result_width: usize, after_width: usize, inc_closure: Inc, dec_result: Dec) callconv(.C) RocList {
return listKeepResult(list, RocResult.isErr, transform, caller, alignment, before_width, result_width, after_width, inc_closure, dec_result);
}
pub fn listKeepResult(list: RocList, is_good_constructor: fn (RocResult) bool, transform: Opaque, caller: Caller1, alignment: usize, before_width: usize, result_width: usize, after_width: usize) RocList {
pub fn listKeepResult(list: RocList, is_good_constructor: fn (RocResult) bool, transform: Opaque, caller: Caller1, alignment: usize, before_width: usize, result_width: usize, after_width: usize, inc_closure: Inc, dec_result: Dec) RocList {
if (list.bytes) |source_ptr| {
const size = list.len();
var i: usize = 0;
@ -200,23 +211,31 @@ pub fn listKeepResult(list: RocList, is_good_constructor: fn (RocResult) bool, t
var kept: usize = 0;
while (i < size) : (i += 1) {
const element = source_ptr + (i * before_width);
caller(transform, element, temporary);
const before_element = source_ptr + (i * before_width);
inc_closure(transform);
caller(transform, before_element, temporary);
const result = utils.RocResult{ .bytes = temporary };
const after_element = temporary + @sizeOf(i64);
if (is_good_constructor(result)) {
@memcpy(target_ptr + (kept * after_width), temporary + @sizeOf(i64), after_width);
@memcpy(target_ptr + (kept * after_width), after_element, after_width);
kept += 1;
} else {
dec_result(temporary);
}
}
output.length = kept;
utils.decref(std.heap.c_allocator, alignment, list.bytes, size * before_width);
std.heap.c_allocator.free(temporary[0..result_width]);
return output;
if (kept == 0) {
utils.decref(std.heap.c_allocator, alignment, output.bytes, size * after_width);
return RocList.empty();
} else {
output.length = kept;
return output;
}
} else {
return RocList.empty();
}
@ -278,3 +297,30 @@ pub fn listContains(list: RocList, key: Opaque, key_width: usize, is_eq: EqFn) c
return false;
}
pub fn listRepeat(count: usize, alignment: usize, element: Opaque, element_width: usize, inc_n_element: Inc) callconv(.C) RocList {
if (count == 0) {
return RocList.empty();
}
const allocator = std.heap.c_allocator;
var output = RocList.allocate(allocator, alignment, count, element_width);
if (output.bytes) |target_ptr| {
var i: usize = 0;
const source = element orelse unreachable;
while (i < count) : (i += 1) {
@memcpy(target_ptr + i * element_width, source, element_width);
}
// TODO do all increments at once!
i = 0;
while (i < count) : (i += 1) {
inc_n_element(element);
}
return output;
} else {
unreachable;
}
}

View file

@ -14,6 +14,7 @@ comptime {
exportListFn(list.listKeepOks, "keep_oks");
exportListFn(list.listKeepErrs, "keep_errs");
exportListFn(list.listContains, "contains");
exportListFn(list.listRepeat, "repeat");
}
// Dict Module
@ -54,6 +55,7 @@ comptime {
// Str Module
const str = @import("str.zig");
comptime {
exportStrFn(str.init, "init");
exportStrFn(str.strSplitInPlaceC, "str_split_in_place");
exportStrFn(str.countSegments, "count_segments");
exportStrFn(str.countGraphemeClusters, "count_grapheme_clusters");
@ -65,6 +67,7 @@ comptime {
exportStrFn(str.strFromIntC, "from_int");
exportStrFn(str.strFromFloatC, "from_float");
exportStrFn(str.strEqual, "equal");
exportStrFn(str.validateUtf8Bytes, "validate_utf8_bytes");
}
// Export helpers - Must be run inside a comptime

View file

@ -1,22 +1,23 @@
const std = @import("std");
const always_inline = std.builtin.CallOptions.Modifier.always_inline;
const math = std.math;
pub fn atan(num: f64) callconv(.C) f64 {
return math.atan(num);
return @call(.{ .modifier = always_inline }, math.atan, .{num});
}
pub fn isFinite(num: f64) callconv(.C) bool {
return math.isFinite(num);
return @call(.{ .modifier = always_inline }, math.isFinite, .{num});
}
pub fn powInt(base: i64, exp: i64) callconv(.C) i64 {
return math.pow(i64, base, exp);
return @call(.{ .modifier = always_inline }, math.pow, .{ i64, base, exp });
}
pub fn acos(num: f64) callconv(.C) f64 {
return math.acos(num);
return @call(.{ .modifier = always_inline }, math.acos, .{num});
}
pub fn asin(num: f64) callconv(.C) f64 {
return math.asin(num);
return @call(.{ .modifier = always_inline }, math.asin, .{num});
}

View file

@ -1,3 +1,4 @@
const utils = @import("utils.zig");
const std = @import("std");
const mem = std.mem;
const always_inline = std.builtin.CallOptions.Modifier.always_inline;
@ -5,6 +6,7 @@ const Allocator = mem.Allocator;
const unicode = std.unicode;
const testing = std.testing;
const expectEqual = testing.expectEqual;
const expectError = testing.expectError;
const expect = testing.expect;
const InPlace = packed enum(u8) {
@ -47,18 +49,7 @@ pub const RocStr = extern struct {
}
pub fn initBig(allocator: *Allocator, in_place: InPlace, number_of_chars: u64) RocStr {
const length = @sizeOf(usize) + number_of_chars;
var new_bytes: []usize = allocator.alloc(usize, length) catch unreachable;
if (in_place == InPlace.InPlace) {
new_bytes[0] = @intCast(usize, number_of_chars);
} else {
const v: isize = std.math.minInt(isize);
new_bytes[0] = @bitCast(usize, v);
}
var first_element = @ptrCast([*]align(@alignOf(usize)) u8, new_bytes);
first_element += @sizeOf(usize);
const first_element = utils.allocateWithRefcount(allocator, @sizeOf(usize), number_of_chars);
return RocStr{
.str_bytes = first_element,
@ -95,6 +86,12 @@ pub const RocStr = extern struct {
}
}
pub fn toSlice(self: RocStr) []u8 {
const str_bytes_ptr: [*]u8 = self.str_bytes orelse unreachable;
const str_bytes: []u8 = str_bytes_ptr[0..self.str_len];
return str_bytes;
}
// This takes ownership of the pointed-to bytes if they won't fit in a
// small string, and returns a (pointer, len) tuple which points to them.
pub fn withCapacity(length: usize) RocStr {
@ -266,6 +263,10 @@ pub const RocStr = extern struct {
}
};
pub fn init(bytes_ptr: [*]const u8, length: usize) callconv(.C) RocStr {
return @call(.{ .modifier = always_inline }, RocStr.init, .{ std.heap.c_allocator, bytes_ptr, length });
}
// Str.equal
pub fn strEqual(self: RocStr, other: RocStr) callconv(.C) bool {
return self.eq(other);
@ -833,8 +834,10 @@ pub fn strConcatC(result_in_place: InPlace, arg1: RocStr, arg2: RocStr) callconv
fn strConcat(allocator: *Allocator, result_in_place: InPlace, arg1: RocStr, arg2: RocStr) RocStr {
if (arg1.isEmpty()) {
// the second argument is borrowed, so we must increment its refcount before returning
return RocStr.clone(allocator, result_in_place, arg2);
} else if (arg2.isEmpty()) {
// the first argument is owned, so we can return it without cloning
return RocStr.clone(allocator, result_in_place, arg1);
} else {
const combined_length = arg1.len() + arg2.len();
@ -957,3 +960,212 @@ test "RocStr.joinWith: result is big" {
expect(roc_result.eq(result));
}
pub fn isValidUnicode(ptr: [*]u8, len: usize) callconv(.C) bool {
const bytes: []u8 = ptr[0..len];
return @call(.{ .modifier = always_inline }, unicode.utf8ValidateSlice, .{bytes});
}
const Utf8DecodeError = error{
UnexpectedEof,
Utf8InvalidStartByte,
Utf8ExpectedContinuation,
Utf8OverlongEncoding,
Utf8EncodesSurrogateHalf,
Utf8CodepointTooLarge,
};
// Essentially unicode.utf8ValidateSlice -> https://github.com/ziglang/zig/blob/0.7.x/lib/std/unicode.zig#L156
// but only for the next codepoint from the index. Then we return the number of bytes of that codepoint.
// TODO: we only ever use the values 0-4, so can we use smaller int than `usize`?
pub fn numberOfNextCodepointBytes(ptr: [*]u8, len: usize, index: usize) Utf8DecodeError!usize {
const codepoint_len = try unicode.utf8ByteSequenceLength(ptr[index]);
const codepoint_end_index = index + codepoint_len;
if (codepoint_end_index > len) {
return error.UnexpectedEof;
}
_ = try unicode.utf8Decode(ptr[index..codepoint_end_index]);
return codepoint_end_index - index;
}
// Return types for validateUtf8Bytes
// Values must be in alphabetical order. That is, lowest values are the first alphabetically.
pub const Utf8ByteProblem = packed enum(u8) {
CodepointTooLarge = 0,
EncodesSurrogateHalf = 1,
ExpectedContinuation = 2,
InvalidStartByte = 3,
OverlongEncoding = 4,
UnexpectedEndOfSequence = 5,
};
pub const ValidateUtf8BytesResult = extern struct {
is_ok: bool, byte_index: usize, problem_code: Utf8ByteProblem
};
const is_ok_utf8_byte_response =
ValidateUtf8BytesResult{ .is_ok = true, .byte_index = 0, .problem_code = Utf8ByteProblem.UnexpectedEndOfSequence };
inline fn toErrUtf8ByteResponse(byte_index: usize, problem_code: Utf8ByteProblem) ValidateUtf8BytesResult {
return ValidateUtf8BytesResult{ .is_ok = false, .byte_index = byte_index, .problem_code = problem_code };
}
// Validate that an array of bytes is valid UTF-8, but if it fails catch & return the error & byte index
pub fn validateUtf8Bytes(ptr: [*]u8, len: usize) callconv(.C) ValidateUtf8BytesResult {
var index: usize = 0;
while (index < len) {
const nextNumBytes = numberOfNextCodepointBytes(ptr, len, index) catch |err| {
return toErrUtf8ByteResponse(
index,
switch (err) {
error.UnexpectedEof => Utf8ByteProblem.UnexpectedEndOfSequence,
error.Utf8InvalidStartByte => Utf8ByteProblem.InvalidStartByte,
error.Utf8ExpectedContinuation => Utf8ByteProblem.ExpectedContinuation,
error.Utf8OverlongEncoding => Utf8ByteProblem.OverlongEncoding,
error.Utf8EncodesSurrogateHalf => Utf8ByteProblem.EncodesSurrogateHalf,
error.Utf8CodepointTooLarge => Utf8ByteProblem.CodepointTooLarge,
},
);
};
index += nextNumBytes;
}
return is_ok_utf8_byte_response;
}
test "validateUtf8Bytes: ascii" {
const str_len = 3;
var str: [str_len]u8 = "abc".*;
const str_ptr: [*]u8 = &str;
expectEqual(is_ok_utf8_byte_response, validateUtf8Bytes(str_ptr, str_len));
}
test "validateUtf8Bytes: unicode œ" {
const str_len = 2;
var str: [str_len]u8 = "œ".*;
const str_ptr: [*]u8 = &str;
expectEqual(is_ok_utf8_byte_response, validateUtf8Bytes(str_ptr, str_len));
}
test "validateUtf8Bytes: unicode ∆" {
const str_len = 3;
var str: [str_len]u8 = "".*;
const str_ptr: [*]u8 = &str;
expectEqual(is_ok_utf8_byte_response, validateUtf8Bytes(str_ptr, str_len));
}
test "validateUtf8Bytes: emoji" {
const str_len = 4;
var str: [str_len]u8 = "💖".*;
const str_ptr: [*]u8 = &str;
expectEqual(is_ok_utf8_byte_response, validateUtf8Bytes(str_ptr, str_len));
}
test "validateUtf8Bytes: unicode ∆ in middle of array" {
const str_len = 9;
var str: [str_len]u8 = "œb∆c¬".*;
const str_ptr: [*]u8 = &str;
expectEqual(is_ok_utf8_byte_response, validateUtf8Bytes(str_ptr, str_len));
}
test "validateUtf8Bytes: invalid start byte" {
// https://github.com/ziglang/zig/blob/0.7.x/lib/std/unicode.zig#L426
const str_len = 4;
var str: [str_len]u8 = "ab\x80c".*;
const str_ptr: [*]u8 = &str;
expectError(error.Utf8InvalidStartByte, numberOfNextCodepointBytes(str_ptr, str_len, 2));
expectEqual(toErrUtf8ByteResponse(2, Utf8ByteProblem.InvalidStartByte), validateUtf8Bytes(str_ptr, str_len));
}
test "validateUtf8Bytes: unexpected eof for 2 byte sequence" {
// https://github.com/ziglang/zig/blob/0.7.x/lib/std/unicode.zig#L426
const str_len = 4;
var str: [str_len]u8 = "abc\xc2".*;
const str_ptr: [*]u8 = &str;
expectError(error.UnexpectedEof, numberOfNextCodepointBytes(str_ptr, str_len, 3));
expectEqual(toErrUtf8ByteResponse(3, Utf8ByteProblem.UnexpectedEndOfSequence), validateUtf8Bytes(str_ptr, str_len));
}
test "validateUtf8Bytes: expected continuation for 2 byte sequence" {
// https://github.com/ziglang/zig/blob/0.7.x/lib/std/unicode.zig#L426
const str_len = 5;
var str: [str_len]u8 = "abc\xc2\x00".*;
const str_ptr: [*]u8 = &str;
expectError(error.Utf8ExpectedContinuation, numberOfNextCodepointBytes(str_ptr, str_len, 3));
expectEqual(toErrUtf8ByteResponse(3, Utf8ByteProblem.ExpectedContinuation), validateUtf8Bytes(str_ptr, str_len));
}
test "validateUtf8Bytes: unexpected eof for 3 byte sequence" {
// https://github.com/ziglang/zig/blob/0.7.x/lib/std/unicode.zig#L430
const str_len = 5;
var str: [str_len]u8 = "abc\xe0\x00".*;
const str_ptr: [*]u8 = &str;
expectError(error.UnexpectedEof, numberOfNextCodepointBytes(str_ptr, str_len, 3));
expectEqual(toErrUtf8ByteResponse(3, Utf8ByteProblem.UnexpectedEndOfSequence), validateUtf8Bytes(str_ptr, str_len));
}
test "validateUtf8Bytes: expected continuation for 3 byte sequence" {
// https://github.com/ziglang/zig/blob/0.7.x/lib/std/unicode.zig#L430
const str_len = 6;
var str: [str_len]u8 = "abc\xe0\xa0\xc0".*;
const str_ptr: [*]u8 = &str;
expectError(error.Utf8ExpectedContinuation, numberOfNextCodepointBytes(str_ptr, str_len, 3));
expectEqual(toErrUtf8ByteResponse(3, Utf8ByteProblem.ExpectedContinuation), validateUtf8Bytes(str_ptr, str_len));
}
test "validateUtf8Bytes: unexpected eof for 4 byte sequence" {
// https://github.com/ziglang/zig/blob/0.7.x/lib/std/unicode.zig#L437
const str_len = 6;
var str: [str_len]u8 = "abc\xf0\x90\x00".*;
const str_ptr: [*]u8 = &str;
expectError(error.UnexpectedEof, numberOfNextCodepointBytes(str_ptr, str_len, 3));
expectEqual(toErrUtf8ByteResponse(3, Utf8ByteProblem.UnexpectedEndOfSequence), validateUtf8Bytes(str_ptr, str_len));
}
test "validateUtf8Bytes: expected continuation for 4 byte sequence" {
// https://github.com/ziglang/zig/blob/0.7.x/lib/std/unicode.zig#L437
const str_len = 7;
var str: [str_len]u8 = "abc\xf0\x90\x80\x00".*;
const str_ptr: [*]u8 = &str;
expectError(error.Utf8ExpectedContinuation, numberOfNextCodepointBytes(str_ptr, str_len, 3));
expectEqual(toErrUtf8ByteResponse(3, Utf8ByteProblem.ExpectedContinuation), validateUtf8Bytes(str_ptr, str_len));
}
test "validateUtf8Bytes: overlong" {
// https://github.com/ziglang/zig/blob/0.7.x/lib/std/unicode.zig#L451
const str_len = 7;
var str: [str_len]u8 = "abc\xf0\x80\x80\x80".*;
const str_ptr: [*]u8 = &str;
expectError(error.Utf8OverlongEncoding, numberOfNextCodepointBytes(str_ptr, str_len, 3));
expectEqual(toErrUtf8ByteResponse(3, Utf8ByteProblem.OverlongEncoding), validateUtf8Bytes(str_ptr, str_len));
}
test "validateUtf8Bytes: codepoint out too large" {
// https://github.com/ziglang/zig/blob/0.7.x/lib/std/unicode.zig#L465
const str_len = 7;
var str: [str_len]u8 = "abc\xf4\x90\x80\x80".*;
const str_ptr: [*]u8 = &str;
expectError(error.Utf8CodepointTooLarge, numberOfNextCodepointBytes(str_ptr, str_len, 3));
expectEqual(toErrUtf8ByteResponse(3, Utf8ByteProblem.CodepointTooLarge), validateUtf8Bytes(str_ptr, str_len));
}
test "validateUtf8Bytes: surrogate halves" {
// https://github.com/ziglang/zig/blob/0.7.x/lib/std/unicode.zig#L468
const str_len = 6;
var str: [str_len]u8 = "abc\xed\xa0\x80".*;
const str_ptr: [*]u8 = &str;
expectError(error.Utf8EncodesSurrogateHalf, numberOfNextCodepointBytes(str_ptr, str_len, 3));
expectEqual(toErrUtf8ByteResponse(3, Utf8ByteProblem.EncodesSurrogateHalf), validateUtf8Bytes(str_ptr, str_len));
}

View file

@ -16,25 +16,25 @@ pub fn decref(
var bytes = bytes_or_null orelse return;
const usizes: [*]usize = @ptrCast([*]usize, @alignCast(8, bytes));
const isizes: [*]isize = @ptrCast([*]isize, @alignCast(8, bytes));
const refcount = (usizes - 1)[0];
const refcount = (isizes - 1)[0];
const refcount_isize = @bitCast(isize, refcount);
switch (alignment) {
16 => {
if (refcount == REFCOUNT_ONE) {
if (refcount == REFCOUNT_ONE_ISIZE) {
allocator.free((bytes - 16)[0 .. 16 + data_bytes]);
} else if (refcount_isize < 0) {
(usizes - 1)[0] = refcount + 1;
(isizes - 1)[0] = refcount - 1;
}
},
else => {
// NOTE enums can currently have an alignment of < 8
if (refcount == REFCOUNT_ONE) {
if (refcount == REFCOUNT_ONE_ISIZE) {
allocator.free((bytes - 8)[0 .. 8 + data_bytes]);
} else if (refcount_isize < 0) {
(usizes - 1)[0] = refcount + 1;
(isizes - 1)[0] = refcount - 1;
}
},
}
@ -72,11 +72,11 @@ pub fn allocateWithRefcount(
var new_bytes: []align(8) u8 = allocator.alignedAlloc(u8, 8, length) catch unreachable;
var as_usize_array = @ptrCast([*]usize, new_bytes);
var as_usize_array = @ptrCast([*]isize, new_bytes);
if (result_in_place) {
as_usize_array[0] = @intCast(usize, number_of_slots);
as_usize_array[0] = @intCast(isize, number_of_slots);
} else {
as_usize_array[0] = REFCOUNT_ONE;
as_usize_array[0] = REFCOUNT_ONE_ISIZE;
}
var as_u8_array = @ptrCast([*]u8, new_bytes);

View file

@ -29,6 +29,7 @@ pub const NUM_ATAN: &str = "roc_builtins.num.atan";
pub const NUM_IS_FINITE: &str = "roc_builtins.num.is_finite";
pub const NUM_POW_INT: &str = "roc_builtins.num.pow_int";
pub const STR_INIT: &str = "roc_builtins.str.init";
pub const STR_COUNT_SEGMENTS: &str = "roc_builtins.str.count_segments";
pub const STR_CONCAT: &str = "roc_builtins.str.concat";
pub const STR_JOIN_WITH: &str = "roc_builtins.str.joinWith";
@ -40,6 +41,7 @@ pub const STR_NUMBER_OF_BYTES: &str = "roc_builtins.str.number_of_bytes";
pub const STR_FROM_INT: &str = "roc_builtins.str.from_int";
pub const STR_FROM_FLOAT: &str = "roc_builtins.str.from_float";
pub const STR_EQUAL: &str = "roc_builtins.str.equal";
pub const STR_VALIDATE_UTF_BYTES: &str = "roc_builtins.str.validate_utf8_bytes";
pub const DICT_HASH: &str = "roc_builtins.dict.hash";
pub const DICT_HASH_STR: &str = "roc_builtins.dict.hash_str";
@ -67,3 +69,4 @@ pub const LIST_KEEP_ERRS: &str = "roc_builtins.list.keep_errs";
pub const LIST_WALK: &str = "roc_builtins.list.walk";
pub const LIST_WALK_BACKWARDS: &str = "roc_builtins.list.walk_backwards";
pub const LIST_CONTAINS: &str = "roc_builtins.list.contains";
pub const LIST_REPEAT: &str = "roc_builtins.list.repeat";

View file

@ -3,4 +3,3 @@
#![allow(clippy::large_enum_variant)]
pub mod bitcode;
pub mod std;
pub mod unique;

View file

@ -4,7 +4,7 @@ use roc_module::symbol::Symbol;
use roc_region::all::Region;
use roc_types::builtin_aliases::{
bool_type, dict_type, float_type, int_type, list_type, nat_type, num_type, ordering_type,
result_type, set_type, str_type, u64_type,
result_type, set_type, str_type, str_utf8_byte_problem_type, u64_type, u8_type,
};
use roc_types::solved_types::SolvedType;
use roc_types::subs::VarId;
@ -563,6 +563,24 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
top_level_function(vec![int_type(flex(TVAR1))], Box::new(str_type())),
);
// fromUtf8 : List U8 -> Result Str [ BadUtf8 Utf8Problem ]*
let bad_utf8 = SolvedType::TagUnion(
vec![(
TagName::Global("BadUtf8".into()),
// vec![str_utf8_problem_type()],
vec![str_utf8_byte_problem_type(), nat_type()],
)],
Box::new(SolvedType::Wildcard),
);
add_type(
Symbol::STR_FROM_UTF8,
top_level_function(
vec![list_type(u8_type())],
Box::new(result_type(str_type(), bad_utf8)),
),
);
// fromFloat : Float a -> Str
add_type(
Symbol::STR_FROM_FLOAT,

View file

@ -1194,6 +1194,27 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
unique_function(vec![int_type(star1, int)], str_type(star2))
});
// fromUtf8 : Attr * (List U8) -> Attr * (Result Str [ BadUtf8 Utf8Problem ]*)
let bad_utf8 = SolvedType::TagUnion(
vec![(
TagName::Global("BadUtf8".into()),
// vec![builtin_aliases::str_utf8_problem_type()],
vec![
builtin_aliases::str_utf8_byte_problem_type(),
builtin_aliases::nat_type(),
],
)],
Box::new(SolvedType::Wildcard),
);
add_type(Symbol::STR_FROM_UTF8, {
let_tvars! { star1, star2, star3, star4 };
unique_function(
vec![u8_type(star1)],
result_type(star2, str_type(star3), lift(star4, bad_utf8)),
)
});
// Result module
// map : Attr * (Result (Attr a e))
@ -1302,6 +1323,11 @@ fn int_type(u: VarId, range: VarId) -> SolvedType {
)
}
#[inline(always)]
fn u8_type(u: VarId) -> SolvedType {
SolvedType::Apply(Symbol::ATTR_ATTR, vec![flex(u), builtin_aliases::u8_type()])
}
#[inline(always)]
fn bool_type(u: VarId) -> SolvedType {
SolvedType::Apply(

View file

@ -61,6 +61,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
STR_ENDS_WITH => str_ends_with,
STR_COUNT_GRAPHEMES => str_count_graphemes,
STR_FROM_INT => str_from_int,
STR_FROM_UTF8 => str_from_utf8,
STR_FROM_FLOAT=> str_from_float,
LIST_LEN => list_len,
LIST_GET => list_get,
@ -189,6 +190,7 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap<Symbol, Def> {
Symbol::STR_ENDS_WITH => str_ends_with,
Symbol::STR_COUNT_GRAPHEMES => str_count_graphemes,
Symbol::STR_FROM_INT => str_from_int,
Symbol::STR_FROM_UTF8 => str_from_utf8,
Symbol::STR_FROM_FLOAT=> str_from_float,
Symbol::LIST_LEN => list_len,
Symbol::LIST_GET => list_get,
@ -1512,6 +1514,110 @@ fn str_from_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
)
}
/// Str.fromUtf8 : List U8 -> Result Str [ BadUtf8 Utf8Problem ]*
fn str_from_utf8(symbol: Symbol, var_store: &mut VarStore) -> Def {
let bytes_var = var_store.fresh();
let bool_var = var_store.fresh();
let record_var = var_store.fresh();
let ret_var = var_store.fresh();
// let arg_2 = RunLowLevel FromUtf8 arg_1
//
// arg_2 :
// { a : Bool -- isOk
// , b : String -- result_str
// , c : Nat -- problem_byte_index
// , d : I8 -- problem_code
// }
//
// if arg_2.a then
// # all is well
// Ok arg_2.str
// else
// # problem
// Err (BadUtf8 { byteIndex: arg_2.byteIndex, problem : arg_2.problem })
let def = crate::def::Def {
loc_pattern: no_region(Pattern::Identifier(Symbol::ARG_2)),
loc_expr: no_region(RunLowLevel {
op: LowLevel::StrFromUtf8,
args: vec![(bytes_var, Var(Symbol::ARG_1))],
ret_var: record_var,
}),
expr_var: record_var,
pattern_vars: SendMap::default(),
annotation: None,
};
let cont = If {
branch_var: ret_var,
cond_var: bool_var,
branches: vec![(
// if-condition
no_region(
// arg_2.c -> Bool
Access {
record_var,
ext_var: var_store.fresh(),
field: "isOk".into(),
field_var: var_store.fresh(),
loc_expr: Box::new(no_region(Var(Symbol::ARG_2))),
},
),
// all is good
no_region(tag(
"Ok",
// arg_2.a -> Str
vec![Access {
record_var,
ext_var: var_store.fresh(),
field: "str".into(),
field_var: var_store.fresh(),
loc_expr: Box::new(no_region(Var(Symbol::ARG_2))),
}],
var_store,
)),
)],
final_else: Box::new(
// bad!!
no_region(tag(
"Err",
vec![tag(
"BadUtf8",
vec![
Access {
record_var,
ext_var: var_store.fresh(),
field: "problem".into(),
field_var: var_store.fresh(),
loc_expr: Box::new(no_region(Var(Symbol::ARG_2))),
},
Access {
record_var,
ext_var: var_store.fresh(),
field: "byteIndex".into(),
field_var: var_store.fresh(),
loc_expr: Box::new(no_region(Var(Symbol::ARG_2))),
},
],
var_store,
)],
var_store,
)),
),
};
let body = LetNonRec(Box::new(def), Box::new(no_region(cont)), ret_var);
defn(
symbol,
vec![(bytes_var, Symbol::ARG_1)],
var_store,
body,
ret_var,
)
}
/// Str.fromFloat : Float * -> Str
fn str_from_float(symbol: Symbol, var_store: &mut VarStore) -> Def {
let float_var = var_store.fresh();
@ -2042,7 +2148,7 @@ fn dict_get(symbol: Symbol, var_store: &mut VarStore) -> Def {
let arg_dict = Symbol::ARG_1;
let arg_key = Symbol::ARG_2;
let temp_record = Symbol::ARG_3;
let temp_record = Symbol::DICT_GET_RESULT;
let bool_var = var_store.fresh();
let flag_var = var_store.fresh();
@ -3009,6 +3115,18 @@ fn tag(name: &'static str, args: Vec<Expr>, var_store: &mut VarStore) -> Expr {
}
}
// #[inline(always)]
// fn record(fields: Vec<(Lowercase, Field)>, var_store: &mut VarStore) -> Expr {
// let mut send_map = SendMap::default();
// for (k, v) in fields {
// send_map.insert(k, v);
// }
// Expr::Record {
// record_var: var_store.fresh(),
// fields: send_map,
// }
// }
#[inline(always)]
fn defn(
fn_name: Symbol,

View file

@ -207,6 +207,15 @@ fn build_transform_caller_help<'a, 'ctx, 'env>(
function_value
}
pub fn build_inc_n_wrapper<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
layout: &Layout<'a>,
n: u64,
) -> FunctionValue<'ctx> {
build_rc_wrapper(env, layout_ids, layout, Mode::Inc(n))
}
pub fn build_inc_wrapper<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,

View file

@ -11,8 +11,8 @@ use crate::llvm::build_list::{
list_walk, list_walk_backwards,
};
use crate::llvm::build_str::{
str_concat, str_count_graphemes, str_ends_with, str_from_float, str_from_int, str_join_with,
str_number_of_bytes, str_split, str_starts_with, CHAR_LAYOUT,
str_concat, str_count_graphemes, str_ends_with, str_from_float, str_from_int, str_from_utf8,
str_join_with, str_number_of_bytes, str_split, str_starts_with, CHAR_LAYOUT,
};
use crate::llvm::compare::{generic_eq, generic_neq};
use crate::llvm::convert::{
@ -928,7 +928,6 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
tag_layout: Layout::Union(UnionLayout::NonRecursive(fields)),
union_size,
tag_id,
tag_name,
..
} => {
let tag_layout = Layout::Union(UnionLayout::NonRecursive(fields));
@ -944,15 +943,10 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
let mut field_types = Vec::with_capacity_in(num_fields, env.arena);
let mut field_vals = Vec::with_capacity_in(num_fields, env.arena);
let tag_field_layouts = if let TagName::Closure(_) = tag_name {
// closures ignore (and do not store) the discriminant
&fields[*tag_id as usize][1..]
} else {
&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()) {
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.
// The layout of the struct expects them to be dropped!
@ -968,7 +962,7 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
);
} else {
// 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);
}
@ -1025,7 +1019,6 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
tag_layout: Layout::Union(UnionLayout::Recursive(fields)),
union_size,
tag_id,
tag_name,
..
} => {
let tag_layout = Layout::Union(UnionLayout::NonRecursive(fields));
@ -1041,12 +1034,7 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
let mut field_types = Vec::with_capacity_in(num_fields, env.arena);
let mut field_vals = Vec::with_capacity_in(num_fields, env.arena);
let tag_field_layouts = if let TagName::Closure(_) = tag_name {
// closures ignore (and do not store) the discriminant
&fields[*tag_id as usize][1..]
} else {
&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()) {
let (val, val_layout) = load_symbol_and_layout(scope, field_symbol);
@ -1189,7 +1177,6 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
}),
union_size,
tag_id,
tag_name,
..
} => {
let tag_layout = Layout::Union(UnionLayout::NonRecursive(fields));
@ -1212,10 +1199,7 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
let mut field_types = Vec::with_capacity_in(num_fields, env.arena);
let mut field_vals = Vec::with_capacity_in(num_fields, env.arena);
let tag_field_layouts = if let TagName::Closure(_) = tag_name {
// closures ignore (and do not store) the discriminant
&fields[*tag_id as usize][1..]
} else {
let tag_field_layouts = {
use std::cmp::Ordering::*;
match tag_id.cmp(&(*nullable_id as u8)) {
Equal => &[] as &[_],
@ -2225,31 +2209,21 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
match modify {
Inc(symbol, inc_amount) => {
match cont {
Refcounting(ModifyRc::Dec(symbol1), contcont)
if *inc_amount == 1 && symbol == symbol1 =>
{
// the inc and dec cancel out
build_exp_stmt(env, layout_ids, scope, parent, contcont)
}
_ => {
let (value, layout) = load_symbol_and_layout(scope, symbol);
let layout = layout.clone();
let (value, layout) = load_symbol_and_layout(scope, symbol);
let layout = layout.clone();
if layout.contains_refcounted() {
increment_refcount_layout(
env,
parent,
layout_ids,
*inc_amount,
value,
&layout,
);
}
build_exp_stmt(env, layout_ids, scope, parent, cont)
}
if layout.contains_refcounted() {
increment_refcount_layout(
env,
parent,
layout_ids,
*inc_amount,
value,
&layout,
);
}
build_exp_stmt(env, layout_ids, scope, parent, cont)
}
Dec(symbol) => {
let (value, layout) = load_symbol_and_layout(scope, symbol);
@ -3455,9 +3429,16 @@ fn function_value_by_name<'a, 'ctx, 'env>(
if symbol.is_builtin() {
panic!("Unrecognized builtin function: {:?}", fn_name)
} else {
panic!(
"Unrecognized non-builtin function: {:?} (symbol: {:?}, layout: {:?})",
// Unrecognized non-builtin function:
eprintln!(
"Unrecognized non-builtin function: {:?}\n\nSymbol: {:?}\nLayout: {:?}\n",
fn_name, symbol, layout
);
eprintln!("Is the function defined? If so, maybe there is a problem with the layout");
panic!(
"Unrecognized non-builtin function: {:?} (symbol: {:?})",
fn_name, symbol,
)
}
})
@ -3562,6 +3543,14 @@ fn run_low_level<'a, 'ctx, 'env>(
str_from_float(env, scope, args[0])
}
StrFromUtf8 => {
// Str.fromInt : Int -> Str
debug_assert_eq!(args.len(), 1);
let original_wrapper = load_symbol(scope, &args[0]).into_struct_value();
str_from_utf8(env, parent, original_wrapper)
}
StrSplit => {
// Str.split : Str, Str -> List Str
debug_assert_eq!(args.len(), 2);
@ -3614,9 +3603,7 @@ fn run_low_level<'a, 'ctx, 'env>(
let list_len = load_symbol(scope, &args[0]).into_int_value();
let (elem, elem_layout) = load_symbol_and_layout(scope, &args[1]);
let inplace = get_inplace_from_layout(layout);
list_repeat(env, inplace, parent, list_len, elem, elem_layout)
list_repeat(env, layout_ids, list_len, elem, elem_layout)
}
ListReverse => {
// List.reverse : List elem -> List elem

View file

@ -747,6 +747,9 @@ pub fn dict_walk<'a, 'ctx, 'env>(
let output_ptr = builder.build_alloca(accum_bt, "output_ptr");
let inc_key_fn = build_inc_wrapper(env, layout_ids, key_layout);
let inc_value_fn = build_inc_wrapper(env, layout_ids, value_layout);
call_void_bitcode_fn(
env,
&[
@ -758,6 +761,8 @@ pub fn dict_walk<'a, 'ctx, 'env>(
key_width.into(),
value_width.into(),
accum_width.into(),
inc_key_fn.as_global_value().as_pointer_value().into(),
inc_value_fn.as_global_value().as_pointer_value().into(),
env.builder.build_bitcast(output_ptr, u8_ptr, "to_opaque"),
],
&bitcode::DICT_WALK,

View file

@ -1,6 +1,7 @@
#![allow(clippy::too_many_arguments)]
use crate::llvm::bitcode::{
build_eq_wrapper, build_transform_caller, call_bitcode_fn, call_void_bitcode_fn,
build_dec_wrapper, build_eq_wrapper, build_inc_wrapper, build_transform_caller,
call_bitcode_fn, call_void_bitcode_fn,
};
use crate::llvm::build::{
allocate_with_refcount_help, build_num_binop, cast_basic_basic, complex_bitcast, Env, InPlace,
@ -53,90 +54,43 @@ pub fn list_single<'a, 'ctx, 'env>(
/// List.repeat : Int, elem -> List elem
pub fn list_repeat<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
inplace: InPlace,
parent: FunctionValue<'ctx>,
layout_ids: &mut LayoutIds<'a>,
list_len: IntValue<'ctx>,
elem: BasicValueEnum<'ctx>,
elem_layout: &Layout<'a>,
element: BasicValueEnum<'ctx>,
element_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
let ctx = env.context;
// list_len > 0
// We have to do a loop below, continuously adding the `elem`
// to the output list `List elem` until we have reached the
// number of repeats. This `comparison` is used to check
// if we need to do any looping; because if we dont, then we
// dont need to allocate memory for the index or the check
// if index != 0
let comparison = builder.build_int_compare(
IntPredicate::UGT,
list_len,
ctx.i64_type().const_int(0, false),
"atleastzero",
let u8_ptr = env.context.i8_type().ptr_type(AddressSpace::Generic);
let element_ptr = builder.build_alloca(element.get_type(), "element_ptr");
env.builder.build_store(element_ptr, element);
let element_width = env
.ptr_int()
.const_int(element_layout.stack_size(env.ptr_bytes) as u64, false);
let alignment = element_layout.alignment_bytes(env.ptr_bytes);
let alignment_iv = env.ptr_int().const_int(alignment as u64, false);
let inc_element_fn = build_inc_wrapper(env, layout_ids, element_layout);
let output = call_bitcode_fn(
env,
&[
list_len.into(),
alignment_iv.into(),
env.builder.build_bitcast(element_ptr, u8_ptr, "to_u8_ptr"),
element_width.into(),
inc_element_fn.as_global_value().as_pointer_value().into(),
],
bitcode::LIST_REPEAT,
);
let build_then = || {
// Allocate space for the new array that we'll copy into.
let list_ptr = allocate_list(env, inplace, elem_layout, list_len);
// TODO check if malloc returned null; if so, runtime error for OOM!
let index_name = "#index";
let start_alloca = builder.build_alloca(ctx.i64_type(), index_name);
// Start at the last element in the list.
let last_elem_index = builder.build_int_sub(
list_len,
ctx.i64_type().const_int(1, false),
"lastelemindex",
);
builder.build_store(start_alloca, last_elem_index);
let loop_bb = ctx.append_basic_block(parent, "loop");
builder.build_unconditional_branch(loop_bb);
builder.position_at_end(loop_bb);
// #index = #index - 1
let curr_index = builder
.build_load(start_alloca, index_name)
.into_int_value();
let next_index =
builder.build_int_sub(curr_index, ctx.i64_type().const_int(1, false), "nextindex");
builder.build_store(start_alloca, next_index);
let elem_ptr =
unsafe { builder.build_in_bounds_gep(list_ptr, &[curr_index], "load_index") };
// Mutate the new array in-place to change the element.
builder.build_store(elem_ptr, elem);
// #index != 0
let end_cond = builder.build_int_compare(
IntPredicate::NE,
ctx.i64_type().const_int(0, false),
curr_index,
"loopcond",
);
let after_bb = ctx.append_basic_block(parent, "afterloop");
builder.build_conditional_branch(end_cond, loop_bb, after_bb);
builder.position_at_end(after_bb);
store_list(env, list_ptr, list_len)
};
let build_else = || empty_polymorphic_list(env);
let struct_type = collection(ctx, env.ptr_bytes);
build_basic_phi2(
env,
parent,
comparison,
build_then,
build_else,
BasicTypeEnum::StructType(struct_type),
complex_bitcast(
env.builder,
output,
collection(env.context, env.ptr_bytes).into(),
"from_i128",
)
}
@ -1028,6 +982,9 @@ pub fn list_keep_if<'a, 'ctx, 'env>(
let alignment = element_layout.alignment_bytes(env.ptr_bytes);
let alignment_iv = env.ptr_int().const_int(alignment as u64, false);
let inc_element_fn = build_inc_wrapper(env, layout_ids, element_layout);
let dec_element_fn = build_dec_wrapper(env, layout_ids, element_layout);
let output = call_bitcode_fn(
env,
&[
@ -1037,6 +994,8 @@ pub fn list_keep_if<'a, 'ctx, 'env>(
stepper_caller.into(),
alignment_iv.into(),
element_width.into(),
inc_element_fn.as_global_value().as_pointer_value().into(),
dec_element_fn.as_global_value().as_pointer_value().into(),
],
&bitcode::LIST_KEEP_IF,
);
@ -1138,6 +1097,9 @@ pub fn list_keep_result<'a, 'ctx, 'env>(
let alignment = before_layout.alignment_bytes(env.ptr_bytes);
let alignment_iv = env.ptr_int().const_int(alignment as u64, false);
let inc_closure = build_inc_wrapper(env, layout_ids, transform_layout);
let dec_result_fn = build_dec_wrapper(env, layout_ids, result_layout);
let output = call_bitcode_fn(
env,
&[
@ -1149,6 +1111,8 @@ pub fn list_keep_result<'a, 'ctx, 'env>(
before_width.into(),
result_width.into(),
after_width.into(),
inc_closure.as_global_value().as_pointer_value().into(),
dec_result_fn.as_global_value().as_pointer_value().into(),
],
op,
);
@ -1532,7 +1496,7 @@ where
"bounds_check",
);
let after_loop_bb = ctx.append_basic_block(parent, "after_outer_loop");
let after_loop_bb = ctx.append_basic_block(parent, "after_outer_loop_1");
builder.build_conditional_branch(condition, loop_bb, after_loop_bb);
builder.position_at_end(after_loop_bb);
@ -1599,7 +1563,7 @@ where
// #index < end
let loop_end_cond = bounds_check_comparison(builder, next_index, end);
let after_loop_bb = ctx.append_basic_block(parent, "after_outer_loop");
let after_loop_bb = ctx.append_basic_block(parent, "after_outer_loop_2");
builder.build_conditional_branch(loop_end_cond, loop_bb, after_loop_bb);
builder.position_at_end(after_loop_bb);

View file

@ -1,11 +1,13 @@
use crate::llvm::bitcode::{call_bitcode_fn, call_void_bitcode_fn};
use crate::llvm::build::{complex_bitcast, Env, InPlace, Scope};
use crate::llvm::build_list::{allocate_list, store_list};
use crate::llvm::convert::collection;
use crate::llvm::build_list::{
allocate_list, build_basic_phi2, empty_polymorphic_list, list_len, load_list_ptr, store_list,
};
use crate::llvm::convert::{collection, get_ptr_type};
use inkwell::builder::Builder;
use inkwell::types::BasicTypeEnum;
use inkwell::values::{BasicValueEnum, IntValue, PointerValue, StructValue};
use inkwell::AddressSpace;
use inkwell::types::{BasicTypeEnum, StructType};
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue};
use inkwell::{AddressSpace, IntPredicate};
use roc_builtins::bitcode;
use roc_module::symbol::Symbol;
use roc_mono::layout::{Builtin, Layout};
@ -273,6 +275,124 @@ pub fn str_from_int<'a, 'ctx, 'env>(
zig_str_to_struct(env, zig_result).into()
}
/// Str.fromUtf8 : List U8 -> { a : Bool, b : Str, c : Nat, d : I8 }
pub fn str_from_utf8<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
parent: FunctionValue<'ctx>,
original_wrapper: StructValue<'ctx>,
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
let ctx = env.context;
let list_len = list_len(builder, original_wrapper);
let ptr_type = get_ptr_type(&ctx.i8_type().into(), AddressSpace::Generic);
let list_ptr = load_list_ptr(builder, original_wrapper, ptr_type);
let result_type = env
.module
.get_struct_type("str.ValidateUtf8BytesResult")
.unwrap();
let result_ptr = builder.build_alloca(result_type, "alloca_utf8_validate_bytes_result");
call_void_bitcode_fn(
env,
&[result_ptr.into(), list_ptr.into(), list_len.into()],
&bitcode::STR_VALIDATE_UTF_BYTES,
);
let utf8_validate_bytes_result = builder
.build_load(result_ptr, "load_utf8_validate_bytes_result")
.into_struct_value();
let is_ok = builder
.build_extract_value(utf8_validate_bytes_result, 0, "extract_extract_is_ok")
.unwrap()
.into_int_value();
let byte_index = builder
.build_extract_value(utf8_validate_bytes_result, 1, "extract_byte_index")
.unwrap()
.into_int_value();
let problem_code = builder
.build_extract_value(utf8_validate_bytes_result, 2, "extract_problem_code")
.unwrap()
.into_int_value();
let record_type = env.context.struct_type(
&[
env.ptr_int().into(),
collection(env.context, env.ptr_bytes).into(),
env.context.bool_type().into(),
ctx.i8_type().into(),
],
false,
);
let comparison = builder.build_int_compare(
IntPredicate::EQ,
is_ok,
ctx.bool_type().const_int(1, false),
"compare_is_ok",
);
build_basic_phi2(
env,
parent,
comparison,
|| {
// We have a valid utf8 byte sequence
// TODO: Should we do something different here if we're doing this in place?
let zig_str =
call_bitcode_fn(env, &[list_ptr.into(), list_len.into()], &bitcode::STR_INIT)
.into_struct_value();
build_struct(
builder,
record_type,
vec![
(
env.ptr_int().const_int(0, false).into(),
"insert_zeroed_byte_index",
),
(zig_str_to_struct(env, zig_str).into(), "insert_str"),
(ctx.bool_type().const_int(1, false).into(), "insert_is_ok"),
(
ctx.i8_type().const_int(0, false).into(),
"insert_zeroed_problem",
),
],
)
.into()
},
|| {
// We do not have a valid utf8 byte sequence
build_struct(
builder,
record_type,
vec![
(byte_index.into(), "insert_byte_index"),
(empty_polymorphic_list(env), "insert_zeroed_str"),
(ctx.bool_type().const_int(0, false).into(), "insert_is_ok"),
(problem_code.into(), "insert_problem"),
],
)
.into()
},
BasicTypeEnum::StructType(record_type),
)
}
fn build_struct<'env, 'ctx>(
builder: &'env Builder<'ctx>,
struct_type: StructType<'ctx>,
values: Vec<(BasicValueEnum<'ctx>, &str)>,
) -> StructValue<'ctx> {
let mut val = struct_type.get_undef().into();
for (index, (value, name)) in values.iter().enumerate() {
val = builder
.build_insert_value(val, *value, index as u32, name)
.unwrap();
}
val.into_struct_value()
}
/// Str.fromInt : Int -> Str
pub fn str_from_float<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,

View file

@ -13,6 +13,7 @@ use inkwell::module::Linkage;
use inkwell::types::{AnyTypeEnum, BasicType, BasicTypeEnum};
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue};
use inkwell::{AddressSpace, IntPredicate};
use roc_module::symbol::Interns;
use roc_module::symbol::Symbol;
use roc_mono::layout::{Builtin, Layout, LayoutIds, MemoryMode, UnionLayout};
@ -284,6 +285,7 @@ fn modify_refcount_struct<'a, 'ctx, 'env>(
value: BasicValueEnum<'ctx>,
layouts: &[Layout<'a>],
mode: Mode,
when_recursive: &WhenRecursive<'a>,
) {
let wrapper_struct = value.into_struct_value();
@ -294,7 +296,15 @@ fn modify_refcount_struct<'a, 'ctx, 'env>(
.build_extract_value(wrapper_struct, i as u32, "decrement_struct_field")
.unwrap();
modify_refcount_layout(env, parent, layout_ids, mode, field_ptr, field_layout);
modify_refcount_layout_help(
env,
parent,
layout_ids,
mode,
when_recursive,
field_ptr,
field_layout,
);
}
}
}
@ -329,9 +339,9 @@ pub fn decrement_refcount_layout<'a, 'ctx, 'env>(
fn modify_refcount_builtin<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
parent: FunctionValue<'ctx>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
when_recursive: &WhenRecursive<'a>,
value: BasicValueEnum<'ctx>,
layout: &Layout<'a>,
builtin: &Builtin<'a>,
@ -341,30 +351,17 @@ fn modify_refcount_builtin<'a, 'ctx, 'env>(
match builtin {
List(memory_mode, element_layout) => {
let wrapper_struct = value.into_struct_value();
if element_layout.contains_refcounted() {
let ptr_type =
basic_type_from_layout(env.arena, env.context, element_layout, env.ptr_bytes)
.ptr_type(AddressSpace::Generic);
let (len, ptr) = load_list(env.builder, wrapper_struct, ptr_type);
let loop_fn = |_index, element| {
modify_refcount_layout(env, parent, layout_ids, mode, element, element_layout);
};
incrementing_elem_loop(
env.builder,
env.context,
parent,
ptr,
len,
"modify_rc_index",
loop_fn,
);
}
if let MemoryMode::Refcounted = memory_mode {
modify_refcount_list(env, layout_ids, mode, layout, wrapper_struct);
modify_refcount_list(
env,
layout_ids,
mode,
when_recursive,
layout,
element_layout,
wrapper_struct,
);
}
}
Set(element_layout) => {
@ -379,6 +376,7 @@ fn modify_refcount_builtin<'a, 'ctx, 'env>(
env,
layout_ids,
mode,
when_recursive,
layout,
key_layout,
value_layout,
@ -403,13 +401,45 @@ fn modify_refcount_layout<'a, 'ctx, 'env>(
mode: Mode,
value: BasicValueEnum<'ctx>,
layout: &Layout<'a>,
) {
modify_refcount_layout_help(
env,
parent,
layout_ids,
mode,
&WhenRecursive::Unreachable,
value,
layout,
);
}
#[derive(Clone, Debug, PartialEq)]
enum WhenRecursive<'a> {
Unreachable,
Loop(UnionLayout<'a>),
}
fn modify_refcount_layout_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
parent: FunctionValue<'ctx>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
when_recursive: &WhenRecursive<'a>,
value: BasicValueEnum<'ctx>,
layout: &Layout<'a>,
) {
use Layout::*;
match layout {
Builtin(builtin) => {
modify_refcount_builtin(env, parent, layout_ids, mode, value, layout, builtin)
}
Builtin(builtin) => modify_refcount_builtin(
env,
layout_ids,
mode,
when_recursive,
value,
layout,
builtin,
),
Union(variant) => {
use UnionLayout::*;
@ -424,6 +454,7 @@ fn modify_refcount_layout<'a, 'ctx, 'env>(
env,
layout_ids,
mode,
&WhenRecursive::Loop(variant.clone()),
tags,
value.into_pointer_value(),
true,
@ -439,6 +470,7 @@ fn modify_refcount_layout<'a, 'ctx, 'env>(
env,
layout_ids,
mode,
&WhenRecursive::Loop(variant.clone()),
&*env.arena.alloc([other_fields]),
value.into_pointer_value(),
true,
@ -452,6 +484,7 @@ fn modify_refcount_layout<'a, 'ctx, 'env>(
env,
layout_ids,
mode,
&WhenRecursive::Loop(variant.clone()),
&*env.arena.alloc([*fields]),
value.into_pointer_value(),
true,
@ -464,13 +497,16 @@ fn modify_refcount_layout<'a, 'ctx, 'env>(
env,
layout_ids,
mode,
&WhenRecursive::Loop(variant.clone()),
tags,
value.into_pointer_value(),
false,
);
}
NonRecursive(tags) => modify_refcount_union(env, layout_ids, mode, tags, value),
NonRecursive(tags) => {
modify_refcount_union(env, layout_ids, mode, when_recursive, tags, value)
}
}
}
Closure(_, closure_layout, _) => {
@ -482,11 +518,12 @@ fn modify_refcount_layout<'a, 'ctx, 'env>(
.build_extract_value(wrapper_struct, 1, "modify_rc_closure_data")
.unwrap();
modify_refcount_layout(
modify_refcount_layout_help(
env,
parent,
layout_ids,
mode,
when_recursive,
field_ptr,
&closure_layout.as_block_of_memory_layout(),
)
@ -494,12 +531,45 @@ fn modify_refcount_layout<'a, 'ctx, 'env>(
}
Struct(layouts) => {
modify_refcount_struct(env, parent, layout_ids, value, layouts, mode);
modify_refcount_struct(
env,
parent,
layout_ids,
value,
layouts,
mode,
when_recursive,
);
}
PhantomEmptyStruct => {}
RecursivePointer => todo!("TODO implement decrement layout of recursive tag union"),
Layout::RecursivePointer => match when_recursive {
WhenRecursive::Unreachable => {
unreachable!("recursion pointers should never be hashed directly")
}
WhenRecursive::Loop(union_layout) => {
let layout = Layout::Union(union_layout.clone());
let bt = basic_type_from_layout(env.arena, env.context, &layout, env.ptr_bytes);
// cast the i64 pointer to a pointer to block of memory
let field_cast = env
.builder
.build_bitcast(value, bt, "i64_to_opaque")
.into_pointer_value();
modify_refcount_layout_help(
env,
parent,
layout_ids,
mode,
when_recursive,
field_cast.into(),
&layout,
)
}
},
FunctionPointer(_, _) | Pointer(_) => {}
}
@ -509,20 +579,22 @@ fn modify_refcount_list<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
when_recursive: &WhenRecursive<'a>,
layout: &Layout<'a>,
element_layout: &Layout<'a>,
original_wrapper: StructValue<'ctx>,
) {
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
let (call_name, symbol) = match mode {
Mode::Inc(_) => ("increment_list", Symbol::INC),
Mode::Dec => ("decrement_list", Symbol::DEC),
};
let fn_name = layout_ids
.get(symbol, &layout)
.to_symbol_string(symbol, &env.interns);
let (call_name, fn_name) = function_name_from_mode(
layout_ids,
&env.interns,
"increment_list",
"decrement_list",
&layout,
mode,
);
let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
@ -530,7 +602,15 @@ fn modify_refcount_list<'a, 'ctx, 'env>(
let basic_type = basic_type_from_layout(env.arena, env.context, &layout, env.ptr_bytes);
let function_value = build_header(env, basic_type, mode, &fn_name);
modify_refcount_list_help(env, mode, layout, function_value);
modify_refcount_list_help(
env,
layout_ids,
mode,
when_recursive,
layout,
element_layout,
function_value,
);
function_value
}
@ -552,8 +632,11 @@ fn mode_to_call_mode(function: FunctionValue<'_>, mode: Mode) -> CallMode<'_> {
fn modify_refcount_list_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
when_recursive: &WhenRecursive<'a>,
layout: &Layout<'a>,
element_layout: &Layout<'a>,
fn_val: FunctionValue<'ctx>,
) {
let builder = env.builder;
@ -592,6 +675,36 @@ fn modify_refcount_list_help<'a, 'ctx, 'env>(
builder.position_at_end(modification_block);
if element_layout.contains_refcounted() {
let ptr_type =
basic_type_from_layout(env.arena, env.context, element_layout, env.ptr_bytes)
.ptr_type(AddressSpace::Generic);
let (len, ptr) = load_list(env.builder, original_wrapper, ptr_type);
let loop_fn = |_index, element| {
modify_refcount_layout_help(
env,
parent,
layout_ids,
mode,
when_recursive,
element,
element_layout,
);
};
incrementing_elem_loop(
env.builder,
env.context,
parent,
ptr,
len,
"modify_rc_index",
loop_fn,
);
}
let refcount_ptr = PointerToRefcount::from_list_wrapper(env, original_wrapper);
let call_mode = mode_to_call_mode(fn_val, mode);
refcount_ptr.modify(call_mode, layout, env);
@ -614,14 +727,14 @@ fn modify_refcount_str<'a, 'ctx, 'env>(
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
let (call_name, symbol) = match mode {
Mode::Inc(_) => ("increment_str", Symbol::INC),
Mode::Dec => ("decrement_str", Symbol::DEC),
};
let fn_name = layout_ids
.get(symbol, &layout)
.to_symbol_string(symbol, &env.interns);
let (call_name, fn_name) = function_name_from_mode(
layout_ids,
&env.interns,
"increment_str",
"decrement_str",
&layout,
mode,
);
let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
@ -700,10 +813,12 @@ fn modify_refcount_str_help<'a, 'ctx, 'env>(
builder.build_return(None);
}
#[allow(clippy::too_many_arguments)]
fn modify_refcount_dict<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
when_recursive: &WhenRecursive<'a>,
layout: &Layout<'a>,
key_layout: &Layout<'a>,
value_layout: &Layout<'a>,
@ -712,14 +827,14 @@ fn modify_refcount_dict<'a, 'ctx, 'env>(
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
let (call_name, symbol) = match mode {
Mode::Inc(_) => ("increment_str", Symbol::INC),
Mode::Dec => ("decrement_str", Symbol::DEC),
};
let fn_name = layout_ids
.get(symbol, &layout)
.to_symbol_string(symbol, &env.interns);
let (call_name, fn_name) = function_name_from_mode(
layout_ids,
&env.interns,
"increment_dict",
"decrement_dict",
&layout,
mode,
);
let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
@ -731,6 +846,7 @@ fn modify_refcount_dict<'a, 'ctx, 'env>(
env,
layout_ids,
mode,
when_recursive,
layout,
key_layout,
value_layout,
@ -748,15 +864,23 @@ fn modify_refcount_dict<'a, 'ctx, 'env>(
call_help(env, function, mode, original_wrapper.into(), call_name);
}
#[allow(clippy::too_many_arguments)]
fn modify_refcount_dict_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
when_recursive: &WhenRecursive<'a>,
layout: &Layout<'a>,
key_layout: &Layout<'a>,
value_layout: &Layout<'a>,
fn_val: FunctionValue<'ctx>,
) {
debug_assert_eq!(
when_recursive,
&WhenRecursive::Unreachable,
"TODO pipe when_recursive through the dict key/value inc/dec"
);
let builder = env.builder;
let ctx = env.context;
@ -783,7 +907,7 @@ fn modify_refcount_dict_help<'a, 'ctx, 'env>(
.into_int_value();
// the block we'll always jump to when we're done
let cont_block = ctx.append_basic_block(parent, "modify_rc_str_cont");
let cont_block = ctx.append_basic_block(parent, "modify_rc_dict_cont");
let modification_block = ctx.append_basic_block(parent, "modify_rc");
let is_non_empty = builder.build_int_compare(
@ -893,20 +1017,21 @@ fn build_rec_union<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
when_recursive: &WhenRecursive<'a>,
fields: &'a [&'a [Layout<'a>]],
value: PointerValue<'ctx>,
is_nullable: bool,
) {
let layout = Layout::Union(UnionLayout::Recursive(fields));
let (call_name, symbol) = match mode {
Mode::Inc(_) => ("increment_rec_union", Symbol::INC),
Mode::Dec => ("decrement_rec_union", Symbol::DEC),
};
let fn_name = layout_ids
.get(symbol, &layout)
.to_symbol_string(symbol, &env.interns);
let (call_name, fn_name) = function_name_from_mode(
layout_ids,
&env.interns,
"increment_rec_union",
"decrement_rec_union",
&layout,
mode,
);
let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
@ -919,7 +1044,15 @@ fn build_rec_union<'a, 'ctx, 'env>(
.into();
let function_value = build_header(env, basic_type, mode, &fn_name);
build_rec_union_help(env, layout_ids, mode, fields, function_value, is_nullable);
build_rec_union_help(
env,
layout_ids,
mode,
when_recursive,
fields,
function_value,
is_nullable,
);
env.builder.position_at_end(block);
env.builder
@ -936,6 +1069,7 @@ fn build_rec_union_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
when_recursive: &WhenRecursive<'a>,
tags: &[&[Layout<'a>]],
fn_val: FunctionValue<'ctx>,
is_nullable: bool,
@ -1092,7 +1226,15 @@ fn build_rec_union_help<'a, 'ctx, 'env>(
refcount_ptr.modify(call_mode, &layout, env);
for (field, field_layout) in deferred_nonrec {
modify_refcount_layout(env, parent, layout_ids, mode, field, field_layout);
modify_refcount_layout_help(
env,
parent,
layout_ids,
mode,
when_recursive,
field,
field_layout,
);
}
let call_name = pick("recursive_tag_increment", "recursive_tag_decrement");
@ -1183,10 +1325,31 @@ fn call_help<'a, 'ctx, 'env>(
call
}
fn function_name_from_mode<'a>(
layout_ids: &mut LayoutIds<'a>,
interns: &Interns,
if_inc: &'static str,
if_dec: &'static str,
layout: &Layout<'a>,
mode: Mode,
) -> (&'static str, String) {
// NOTE this is not a typo, we always determine the layout ID
// using the DEC symbol. Anything that is incrementing must also be
// decremented, so `dec` is used on more layouts. That can cause the
// layout ids of the inc and dec versions to be different, which is
// rather confusing, so now `inc_x` always corresponds to `dec_x`
let layout_id = layout_ids.get(Symbol::DEC, layout);
match mode {
Mode::Inc(_) => (if_inc, layout_id.to_symbol_string(Symbol::INC, interns)),
Mode::Dec => (if_dec, layout_id.to_symbol_string(Symbol::DEC, interns)),
}
}
fn modify_refcount_union<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
when_recursive: &WhenRecursive<'a>,
fields: &'a [&'a [Layout<'a>]],
value: BasicValueEnum<'ctx>,
) {
@ -1195,14 +1358,14 @@ fn modify_refcount_union<'a, 'ctx, 'env>(
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
let (call_name, symbol) = match mode {
Mode::Inc(_) => ("increment_union", Symbol::INC),
Mode::Dec => ("decrement_union", Symbol::DEC),
};
let fn_name = layout_ids
.get(symbol, &layout)
.to_symbol_string(symbol, &env.interns);
let (call_name, fn_name) = function_name_from_mode(
layout_ids,
&env.interns,
"increment_union",
"decrement_union",
&layout,
mode,
);
let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
@ -1210,7 +1373,14 @@ fn modify_refcount_union<'a, 'ctx, 'env>(
let basic_type = block_of_memory(env.context, &layout, env.ptr_bytes);
let function_value = build_header(env, basic_type, mode, &fn_name);
modify_refcount_union_help(env, layout_ids, mode, fields, function_value);
modify_refcount_union_help(
env,
layout_ids,
mode,
when_recursive,
fields,
function_value,
);
function_value
}
@ -1227,6 +1397,7 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
when_recursive: &WhenRecursive<'a>,
tags: &[&[Layout<'a>]],
fn_val: FunctionValue<'ctx>,
) {
@ -1311,7 +1482,15 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>(
.build_extract_value(wrapper_struct, i as u32, "modify_tag_field")
.unwrap();
modify_refcount_layout(env, parent, layout_ids, mode, field_ptr, field_layout);
modify_refcount_layout_help(
env,
parent,
layout_ids,
mode,
when_recursive,
field_ptr,
field_layout,
);
}
}

View file

@ -516,6 +516,225 @@ mod gen_str {
assert_evals_to!(r#"Str.fromInt Num.minInt"#, &min, &'static str);
}
#[test]
fn str_from_utf8_pass_single_ascii() {
assert_evals_to!(
indoc!(
r#"
when Str.fromUtf8 [ 97 ] is
Ok val -> val
Err _ -> ""
"#
),
roc_std::RocStr::from_slice("a".as_bytes()),
roc_std::RocStr
);
}
#[test]
fn str_from_utf8_pass_many_ascii() {
assert_evals_to!(
indoc!(
r#"
when Str.fromUtf8 [ 97, 98, 99, 0x7E ] is
Ok val -> val
Err _ -> ""
"#
),
roc_std::RocStr::from_slice("abc~".as_bytes()),
roc_std::RocStr
);
}
#[test]
fn str_from_utf8_pass_single_unicode() {
assert_evals_to!(
indoc!(
r#"
when Str.fromUtf8 [ 0xE2, 0x88, 0x86 ] is
Ok val -> val
Err _ -> ""
"#
),
roc_std::RocStr::from_slice("".as_bytes()),
roc_std::RocStr
);
}
#[test]
fn str_from_utf8_pass_many_unicode() {
assert_evals_to!(
indoc!(
r#"
when Str.fromUtf8 [ 0xE2, 0x88, 0x86, 0xC5, 0x93, 0xC2, 0xAC ] is
Ok val -> val
Err _ -> ""
"#
),
roc_std::RocStr::from_slice("∆œ¬".as_bytes()),
roc_std::RocStr
);
}
#[test]
fn str_from_utf8_pass_single_grapheme() {
assert_evals_to!(
indoc!(
r#"
when Str.fromUtf8 [ 0xF0, 0x9F, 0x92, 0x96 ] is
Ok val -> val
Err _ -> ""
"#
),
roc_std::RocStr::from_slice("💖".as_bytes()),
roc_std::RocStr
);
}
#[test]
fn str_from_utf8_pass_many_grapheme() {
assert_evals_to!(
indoc!(
r#"
when Str.fromUtf8 [ 0xF0, 0x9F, 0x92, 0x96, 0xF0, 0x9F, 0xA4, 0xA0, 0xF0, 0x9F, 0x9A, 0x80 ] is
Ok val -> val
Err _ -> ""
"#
),
roc_std::RocStr::from_slice("💖🤠🚀".as_bytes()),
roc_std::RocStr
);
}
#[test]
fn str_from_utf8_pass_all() {
assert_evals_to!(
indoc!(
r#"
when Str.fromUtf8 [ 0xF0, 0x9F, 0x92, 0x96, 98, 0xE2, 0x88, 0x86 ] is
Ok val -> val
Err _ -> ""
"#
),
roc_std::RocStr::from_slice("💖b∆".as_bytes()),
roc_std::RocStr
);
}
#[test]
fn str_from_utf8_fail_invalid_start_byte() {
assert_evals_to!(
indoc!(
r#"
when Str.fromUtf8 [ 97, 98, 0x80, 99 ] is
Err (BadUtf8 InvalidStartByte byteIndex) ->
if byteIndex == 2 then
"a"
else
"b"
_ -> ""
"#
),
roc_std::RocStr::from_slice("a".as_bytes()),
roc_std::RocStr
);
}
#[test]
fn str_from_utf8_fail_unexpected_end_of_sequence() {
assert_evals_to!(
indoc!(
r#"
when Str.fromUtf8 [ 97, 98, 99, 0xC2 ] is
Err (BadUtf8 UnexpectedEndOfSequence byteIndex) ->
if byteIndex == 3 then
"a"
else
"b"
_ -> ""
"#
),
roc_std::RocStr::from_slice("a".as_bytes()),
roc_std::RocStr
);
}
#[test]
fn str_from_utf8_fail_expected_continuation() {
assert_evals_to!(
indoc!(
r#"
when Str.fromUtf8 [ 97, 98, 99, 0xC2, 0x00 ] is
Err (BadUtf8 ExpectedContinuation byteIndex) ->
if byteIndex == 3 then
"a"
else
"b"
_ -> ""
"#
),
roc_std::RocStr::from_slice("a".as_bytes()),
roc_std::RocStr
);
}
#[test]
fn str_from_utf8_fail_overlong_encoding() {
assert_evals_to!(
indoc!(
r#"
when Str.fromUtf8 [ 97, 0xF0, 0x80, 0x80, 0x80 ] is
Err (BadUtf8 OverlongEncoding byteIndex) ->
if byteIndex == 1 then
"a"
else
"b"
_ -> ""
"#
),
roc_std::RocStr::from_slice("a".as_bytes()),
roc_std::RocStr
);
}
#[test]
fn str_from_utf8_fail_codepoint_too_large() {
assert_evals_to!(
indoc!(
r#"
when Str.fromUtf8 [ 97, 0xF4, 0x90, 0x80, 0x80 ] is
Err (BadUtf8 CodepointTooLarge byteIndex) ->
if byteIndex == 1 then
"a"
else
"b"
_ -> ""
"#
),
roc_std::RocStr::from_slice("a".as_bytes()),
roc_std::RocStr
);
}
#[test]
fn str_from_utf8_fail_surrogate_half() {
assert_evals_to!(
indoc!(
r#"
when Str.fromUtf8 [ 97, 98, 0xED, 0xA0, 0x80 ] is
Err (BadUtf8 EncodesSurrogateHalf byteIndex) ->
if byteIndex == 2 then
"a"
else
"b"
_ -> ""
"#
),
roc_std::RocStr::from_slice("a".as_bytes()),
roc_std::RocStr
);
}
#[test]
fn str_equality() {
assert_evals_to!(r#""a" == "a""#, true, bool);

View file

@ -752,7 +752,6 @@ enum Msg<'a> {
layout_cache: LayoutCache<'a>,
external_specializations_requested: MutMap<ModuleId, ExternalSpecializations>,
procedures: MutMap<(Symbol, Layout<'a>), Proc<'a>>,
passed_by_pointer: MutMap<(Symbol, Layout<'a>), Symbol>,
problems: Vec<roc_mono::ir::MonoProblem>,
module_timing: ModuleTiming,
subs: Subs,
@ -791,7 +790,6 @@ struct State<'a> {
pub module_cache: ModuleCache<'a>,
pub dependencies: Dependencies<'a>,
pub procedures: MutMap<(Symbol, Layout<'a>), Proc<'a>>,
pub passed_by_pointer: MutMap<(Symbol, Layout<'a>), Symbol>,
pub exposed_to_host: MutMap<Symbol, Variable>,
/// This is the "final" list of IdentIds, after canonicalization and constraint gen
@ -1417,7 +1415,6 @@ where
module_cache: ModuleCache::default(),
dependencies: Dependencies::default(),
procedures: MutMap::default(),
passed_by_pointer: MutMap::default(),
exposed_to_host: MutMap::default(),
exposed_types,
headers_parsed,
@ -1957,7 +1954,6 @@ fn update<'a>(
mut ident_ids,
subs,
procedures,
passed_by_pointer,
external_specializations_requested,
problems,
module_timing,
@ -1972,17 +1968,12 @@ fn update<'a>(
.notify(module_id, Phase::MakeSpecializations);
state.procedures.extend(procedures);
state.passed_by_pointer.extend(passed_by_pointer);
state.timings.insert(module_id, module_timing);
if state.dependencies.solved_all() && state.goal_phase == Phase::MakeSpecializations {
debug_assert!(work.is_empty(), "still work remaining {:?}", &work);
Proc::insert_refcount_operations(
arena,
&mut state.procedures,
&state.passed_by_pointer,
);
Proc::insert_refcount_operations(arena, &mut state.procedures);
Proc::optimize_refcount_operations(
arena,
@ -3735,7 +3726,7 @@ fn make_specializations<'a>(
);
let external_specializations_requested = procs.externals_we_need.clone();
let (procedures, passed_by_pointer) = procs.get_specialized_procs_without_rc(mono_env.arena);
let procedures = procs.get_specialized_procs_without_rc(mono_env.arena);
let make_specializations_end = SystemTime::now();
module_timing.make_specializations = make_specializations_end
@ -3747,7 +3738,6 @@ fn make_specializations<'a>(
ident_ids,
layout_cache,
procedures,
passed_by_pointer,
problems: mono_problems,
subs,
external_specializations_requested,

View file

@ -11,6 +11,7 @@ pub enum LowLevel {
StrSplit,
StrCountGraphemes,
StrFromInt,
StrFromUtf8,
StrFromFloat,
ListLen,
ListGetUnsafe,

View file

@ -873,6 +873,9 @@ define_builtins! {
9 STR_ENDS_WITH: "endsWith"
10 STR_FROM_INT: "fromInt"
11 STR_FROM_FLOAT: "fromFloat"
12 STR_FROM_UTF8: "fromUtf8"
13 STR_UT8_PROBLEM: "Utf8Problem" // the Utf8Problem type alias
14 STR_UT8_BYTE_PROBLEM: "Utf8ByteProblem" // the Utf8ByteProblem type alias
}
4 LIST: "List" => {
0 LIST_LIST: "List" imported // the List.List type alias
@ -912,23 +915,24 @@ define_builtins! {
2 DICT_EMPTY: "empty"
3 DICT_SINGLETON: "singleton"
4 DICT_GET: "get"
5 DICT_INSERT: "insert"
6 DICT_LEN: "len"
5 DICT_GET_RESULT: "#get_result" // symbol used in the definition of Dict.get
6 DICT_WALK: "walk"
7 DICT_INSERT: "insert"
8 DICT_LEN: "len"
// This should not be exposed to users, its for testing the
// hash function ONLY
7 DICT_TEST_HASH: "hashTestOnly"
9 DICT_TEST_HASH: "hashTestOnly"
8 DICT_REMOVE: "remove"
9 DICT_CONTAINS: "contains"
10 DICT_KEYS: "keys"
11 DICT_VALUES: "values"
10 DICT_REMOVE: "remove"
11 DICT_CONTAINS: "contains"
12 DICT_KEYS: "keys"
13 DICT_VALUES: "values"
12 DICT_UNION: "union"
13 DICT_INTERSECTION: "intersection"
14 DICT_DIFFERENCE: "difference"
14 DICT_UNION: "union"
15 DICT_INTERSECTION: "intersection"
16 DICT_DIFFERENCE: "difference"
15 DICT_WALK: "walk"
}
7 SET: "Set" => {

View file

@ -16,23 +16,13 @@ fn should_borrow_layout(layout: &Layout) -> bool {
pub fn infer_borrow<'a>(
arena: &'a Bump,
procs: &MutMap<(Symbol, Layout<'a>), Proc<'a>>,
passed_by_pointer: &MutMap<(Symbol, Layout<'a>), Symbol>,
) -> ParamMap<'a> {
let mut param_map = ParamMap {
items: MutMap::default(),
};
for (key, other) in passed_by_pointer {
if let Some(proc) = procs.get(key) {
let mut proc: Proc = proc.clone();
proc.name = *other;
param_map.visit_proc_always_owned(arena, &proc);
}
}
for proc in procs.values() {
param_map.visit_proc(arena, proc);
for (key, proc) in procs {
param_map.visit_proc(arena, proc, key.clone());
}
let mut env = BorrowInfState {
@ -56,8 +46,8 @@ pub fn infer_borrow<'a>(
// TODO in the future I think we need to do this properly, and group
// mutually recursive functions (or just make all their arguments owned)
for proc in procs.values() {
env.collect_proc(proc);
for (key, proc) in procs {
env.collect_proc(proc, key.1.clone());
}
if !env.modified {
@ -73,19 +63,19 @@ pub fn infer_borrow<'a>(
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub enum Key {
Declaration(Symbol),
pub enum Key<'a> {
Declaration(Symbol, Layout<'a>),
JoinPoint(JoinPointId),
}
#[derive(Debug, Clone, Default)]
pub struct ParamMap<'a> {
items: MutMap<Key, &'a [Param<'a>]>,
items: MutMap<Key<'a>, &'a [Param<'a>]>,
}
impl<'a> IntoIterator for ParamMap<'a> {
type Item = (Key, &'a [Param<'a>]);
type IntoIter = <std::collections::HashMap<Key, &'a [Param<'a>]> as IntoIterator>::IntoIter;
type Item = (Key<'a>, &'a [Param<'a>]);
type IntoIter = <std::collections::HashMap<Key<'a>, &'a [Param<'a>]> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.items.into_iter()
@ -93,8 +83,9 @@ impl<'a> IntoIterator for ParamMap<'a> {
}
impl<'a> IntoIterator for &'a ParamMap<'a> {
type Item = (&'a Key, &'a &'a [Param<'a>]);
type IntoIter = <&'a std::collections::HashMap<Key, &'a [Param<'a>]> as IntoIterator>::IntoIter;
type Item = (&'a Key<'a>, &'a &'a [Param<'a>]);
type IntoIter =
<&'a std::collections::HashMap<Key<'a>, &'a [Param<'a>]> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.items.iter()
@ -102,8 +93,8 @@ impl<'a> IntoIterator for &'a ParamMap<'a> {
}
impl<'a> ParamMap<'a> {
pub fn get_symbol(&self, symbol: Symbol) -> Option<&'a [Param<'a>]> {
let key = Key::Declaration(symbol);
pub fn get_symbol(&self, symbol: Symbol, layout: Layout<'a>) -> Option<&'a [Param<'a>]> {
let key = Key::Declaration(symbol, layout);
self.items.get(&key).copied()
}
@ -157,20 +148,31 @@ impl<'a> ParamMap<'a> {
.into_bump_slice()
}
fn visit_proc(&mut self, arena: &'a Bump, proc: &Proc<'a>) {
self.items.insert(
Key::Declaration(proc.name),
fn visit_proc(&mut self, arena: &'a Bump, proc: &Proc<'a>, key: (Symbol, Layout<'a>)) {
if proc.must_own_arguments {
self.visit_proc_always_owned(arena, proc, key);
return;
}
let already_in_there = self.items.insert(
Key::Declaration(proc.name, key.1),
Self::init_borrow_args(arena, proc.args),
);
debug_assert!(already_in_there.is_none());
self.visit_stmt(arena, proc.name, &proc.body);
}
fn visit_proc_always_owned(&mut self, arena: &'a Bump, proc: &Proc<'a>) {
self.items.insert(
Key::Declaration(proc.name),
fn visit_proc_always_owned(
&mut self,
arena: &'a Bump,
proc: &Proc<'a>,
key: (Symbol, Layout<'a>),
) {
let already_in_there = self.items.insert(
Key::Declaration(proc.name, key.1),
Self::init_borrow_args_always_owned(arena, proc.args),
);
debug_assert!(already_in_there.is_none());
self.visit_stmt(arena, proc.name, &proc.body);
}
@ -188,8 +190,10 @@ impl<'a> ParamMap<'a> {
remainder: v,
continuation: b,
} => {
self.items
let already_in_there = self
.items
.insert(Key::JoinPoint(*j), Self::init_borrow_params(arena, xs));
debug_assert!(already_in_there.is_none());
stack.push(v);
stack.push(b);
@ -252,7 +256,7 @@ impl<'a> BorrowInfState<'a> {
}
}
fn update_param_map(&mut self, k: Key) {
fn update_param_map(&mut self, k: Key<'a>) {
let arena = self.arena;
if let Some(ps) = self.param_map.items.get(&k) {
let ps = Vec::from_iter_in(
@ -359,9 +363,11 @@ impl<'a> BorrowInfState<'a> {
} = e;
match call_type {
ByName { name, .. } => {
ByName {
name, full_layout, ..
} => {
// get the borrow signature of the applied function
match self.param_map.get_symbol(*name) {
match self.param_map.get_symbol(*name, full_layout.clone()) {
Some(ps) => {
// the return value will be owned
self.own_var(z);
@ -458,7 +464,12 @@ impl<'a> BorrowInfState<'a> {
match (v, b) {
(
Expr::Call(crate::ir::Call {
call_type: crate::ir::CallType::ByName { name: g, .. },
call_type:
crate::ir::CallType::ByName {
name: g,
full_layout,
..
},
arguments: ys,
..
}),
@ -466,7 +477,12 @@ impl<'a> BorrowInfState<'a> {
)
| (
Expr::Call(crate::ir::Call {
call_type: crate::ir::CallType::ByPointer { name: g, .. },
call_type:
crate::ir::CallType::ByPointer {
name: g,
full_layout,
..
},
arguments: ys,
..
}),
@ -475,7 +491,7 @@ impl<'a> BorrowInfState<'a> {
if self.current_proc == *g && x == *z {
// anonymous functions (for which the ps may not be known)
// can never be tail-recursive, so this is fine
if let Some(ps) = self.param_map.get_symbol(*g) {
if let Some(ps) = self.param_map.get_symbol(*g, full_layout.clone()) {
self.own_params_using_args(ys, ps)
}
}
@ -517,8 +533,10 @@ impl<'a> BorrowInfState<'a> {
Let(x, Expr::FunctionPointer(fsymbol, layout), _, b) => {
// ensure that the function pointed to is in the param map
if let Some(params) = self.param_map.get_symbol(*fsymbol) {
self.param_map.items.insert(Key::Declaration(*x), params);
if let Some(params) = self.param_map.get_symbol(*fsymbol, layout.clone()) {
self.param_map
.items
.insert(Key::Declaration(*x, layout.clone()), params);
}
self.collect_stmt(b);
@ -574,7 +592,7 @@ impl<'a> BorrowInfState<'a> {
}
}
fn collect_proc(&mut self, proc: &Proc<'a>) {
fn collect_proc(&mut self, proc: &Proc<'a>, layout: Layout<'a>) {
let old = self.param_set.clone();
let ys = Vec::from_iter_in(proc.args.iter().map(|t| t.1), self.arena).into_bump_slice();
@ -585,14 +603,16 @@ impl<'a> BorrowInfState<'a> {
self.owned.entry(proc.name).or_default();
self.collect_stmt(&proc.body);
self.update_param_map(Key::Declaration(proc.name));
self.update_param_map(Key::Declaration(proc.name, layout));
self.param_set = old;
}
}
pub fn foreign_borrow_signature(arena: &Bump, arity: usize) -> &[bool] {
let all = bumpalo::vec![in arena; false; arity];
// NOTE this means that Roc is responsible for cleaning up resources;
// the host cannot (currently) take ownership
let all = bumpalo::vec![in arena; true; arity];
all.into_bump_slice()
}
@ -614,16 +634,16 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
ListSet => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),
ListSetInPlace => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),
ListGetUnsafe => arena.alloc_slice_copy(&[borrowed, irrelevant]),
ListConcat | StrConcat => arena.alloc_slice_copy(&[owned, borrowed]),
ListConcat | StrConcat => arena.alloc_slice_copy(&[borrowed, borrowed]),
StrSplit => arena.alloc_slice_copy(&[borrowed, borrowed]),
ListSingle => arena.alloc_slice_copy(&[irrelevant]),
ListRepeat => arena.alloc_slice_copy(&[irrelevant, irrelevant]),
ListRepeat => arena.alloc_slice_copy(&[irrelevant, borrowed]),
ListReverse => arena.alloc_slice_copy(&[owned]),
ListPrepend => arena.alloc_slice_copy(&[owned, owned]),
StrJoinWith => arena.alloc_slice_copy(&[irrelevant, irrelevant]),
StrJoinWith => arena.alloc_slice_copy(&[borrowed, borrowed]),
ListJoin => arena.alloc_slice_copy(&[irrelevant]),
ListMap | ListMapWithIndex => arena.alloc_slice_copy(&[owned, irrelevant]),
ListKeepIf | ListKeepOks | ListKeepErrs => arena.alloc_slice_copy(&[owned, irrelevant]),
ListKeepIf | ListKeepOks | ListKeepErrs => arena.alloc_slice_copy(&[owned, borrowed]),
ListContains => arena.alloc_slice_copy(&[borrowed, irrelevant]),
ListWalk => arena.alloc_slice_copy(&[owned, irrelevant, owned]),
ListWalkBackwards => arena.alloc_slice_copy(&[owned, irrelevant, owned]),
@ -633,9 +653,11 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
// List.append should own its first argument
ListAppend => arena.alloc_slice_copy(&[borrowed, owned]),
Eq | NotEq | And | Or | NumAdd | NumAddWrap | NumAddChecked | NumSub | NumSubWrap
| NumSubChecked | NumMul | NumMulWrap | NumMulChecked | NumGt | NumGte | NumLt | NumLte
| NumCompare | NumDivUnchecked | NumRemUnchecked | NumPow | NumPowInt | NumBitwiseAnd
Eq | NotEq => arena.alloc_slice_copy(&[borrowed, borrowed]),
And | Or | NumAdd | NumAddWrap | NumAddChecked | NumSub | NumSubWrap | NumSubChecked
| NumMul | NumMulWrap | NumMulChecked | NumGt | NumGte | NumLt | NumLte | NumCompare
| NumDivUnchecked | NumRemUnchecked | NumPow | NumPowInt | NumBitwiseAnd
| NumBitwiseXor => arena.alloc_slice_copy(&[irrelevant, irrelevant]),
NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumRound | NumCeiling | NumFloor
@ -643,6 +665,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
arena.alloc_slice_copy(&[irrelevant])
}
StrStartsWith | StrEndsWith => arena.alloc_slice_copy(&[owned, borrowed]),
StrFromUtf8 => arena.alloc_slice_copy(&[owned]),
StrFromInt | StrFromFloat => arena.alloc_slice_copy(&[irrelevant]),
Hash => arena.alloc_slice_copy(&[borrowed, irrelevant]),
DictSize => arena.alloc_slice_copy(&[borrowed]),

View file

@ -232,7 +232,7 @@ impl<'a> Context<'a> {
let mut vars = MutMap::default();
for (key, _) in param_map.into_iter() {
if let crate::borrow::Key::Declaration(symbol) = key {
if let crate::borrow::Key::Declaration(symbol, _) = key {
vars.insert(
*symbol,
VarInfo {
@ -466,9 +466,11 @@ impl<'a> Context<'a> {
&*self.arena.alloc(Stmt::Let(z, v, l, b))
}
ByName { name, .. } => {
ByName {
name, full_layout, ..
} => {
// get the borrow signature
match self.param_map.get_symbol(*name) {
match self.param_map.get_symbol(*name, full_layout.clone()) {
Some(ps) => {
let v = Expr::Call(crate::ir::Call {
call_type,
@ -601,11 +603,8 @@ impl<'a> Context<'a> {
persistent: bool,
consume: bool,
) -> Self {
// can this type be reference-counted at runtime?
let reference = match layout {
Layout::Closure(_, closure, _) => closure.layout.contains_refcounted(),
_ => layout.contains_refcounted(),
};
// should we perform incs and decs on this value?
let reference = layout.contains_refcounted();
let info = VarInfo {
reference,
@ -730,8 +729,12 @@ impl<'a> Context<'a> {
layout,
} => {
// TODO this combines parts of Let and Switch. Did this happen correctly?
let mut case_live_vars = collect_stmt(stmt, &self.jp_live_vars, MutSet::default());
let mut case_live_vars = collect_stmt(pass, &self.jp_live_vars, MutSet::default());
case_live_vars.extend(collect_stmt(fail, &self.jp_live_vars, MutSet::default()));
// the result of an invoke should not be touched in the fail branch
// but it should be present in the pass branch (otherwise it would be dead)
debug_assert!(case_live_vars.contains(symbol));
case_live_vars.remove(symbol);
let fail = {
@ -759,9 +762,50 @@ impl<'a> Context<'a> {
layout: layout.clone(),
};
let stmt = self.arena.alloc(invoke);
let cont = self.arena.alloc(invoke);
(stmt, case_live_vars)
use crate::ir::CallType;
let stmt = match &call.call_type {
CallType::LowLevel { op } => {
let ps = crate::borrow::lowlevel_borrow_signature(self.arena, *op);
self.add_dec_after_lowlevel(call.arguments, ps, cont, &case_live_vars)
}
CallType::Foreign { .. } => {
let ps = crate::borrow::foreign_borrow_signature(
self.arena,
call.arguments.len(),
);
self.add_dec_after_lowlevel(call.arguments, ps, cont, &case_live_vars)
}
CallType::ByName {
name, full_layout, ..
} => {
// get the borrow signature
match self.param_map.get_symbol(*name, full_layout.clone()) {
Some(ps) => self.add_dec_after_application(
call.arguments,
ps,
cont,
&case_live_vars,
),
None => self.add_inc_before_consume_all(
call.arguments,
cont,
&case_live_vars,
),
}
}
CallType::ByPointer { .. } => {
self.add_inc_before_consume_all(call.arguments, cont, &case_live_vars)
}
};
let mut invoke_live_vars = case_live_vars;
occuring_variables_call(call, &mut invoke_live_vars);
(stmt, invoke_live_vars)
}
Join {
id: j,
@ -1005,10 +1049,15 @@ pub fn visit_declaration<'a>(
ctx.add_dec_for_dead_params(params, b, &b_live_vars)
}
pub fn visit_proc<'a>(arena: &'a Bump, param_map: &'a ParamMap<'a>, proc: &mut Proc<'a>) {
pub fn visit_proc<'a>(
arena: &'a Bump,
param_map: &'a ParamMap<'a>,
proc: &mut Proc<'a>,
layout: Layout<'a>,
) {
let ctx = Context::new(arena, param_map);
let params = match param_map.get_symbol(proc.name) {
let params = match param_map.get_symbol(proc.name, layout) {
Some(slice) => slice,
None => Vec::from_iter_in(
proc.args.iter().cloned().map(|(layout, symbol)| Param {

View file

@ -1,8 +1,8 @@
use self::InProgressProc::*;
use crate::exhaustive::{Ctor, Guard, RenderAs, TagId};
use crate::layout::{
Builtin, ClosureLayout, Layout, LayoutCache, LayoutProblem, UnionLayout, WrappedVariant,
TAG_SIZE,
BuildClosureData, Builtin, ClosureLayout, Layout, LayoutCache, LayoutProblem, UnionLayout,
WrappedVariant, TAG_SIZE,
};
use bumpalo::collections::Vec;
use bumpalo::Bump;
@ -118,6 +118,7 @@ pub struct Proc<'a> {
pub closure_data_layout: Option<Layout<'a>>,
pub ret_layout: Layout<'a>,
pub is_self_recursive: SelfRecursive,
pub must_own_arguments: bool,
pub host_exposed_layouts: HostExposedLayouts<'a>,
}
@ -195,31 +196,11 @@ impl<'a> Proc<'a> {
pub fn insert_refcount_operations(
arena: &'a Bump,
procs: &mut MutMap<(Symbol, Layout<'a>), Proc<'a>>,
_passed_by_pointer: &MutMap<(Symbol, Layout<'a>), Symbol>,
) {
// currently we ignore the passed-by-pointerness
let passed_by_pointer = &Default::default();
let borrow_params = arena.alloc(crate::borrow::infer_borrow(arena, procs));
let borrow_params =
arena.alloc(crate::borrow::infer_borrow(arena, procs, passed_by_pointer));
for (key, other) in passed_by_pointer {
if let Some(proc) = procs.get(key) {
let mut proc: Proc = proc.clone();
proc.name = *other;
let layout = key.1.clone();
procs.insert((*other, layout), proc);
} else {
unreachable!(
"we need a by-pointer version of {:?}, but its by-name version does not exist",
key.0
)
}
}
for (_, proc) in procs.iter_mut() {
crate::inc_dec::visit_proc(arena, borrow_params, proc);
for (key, proc) in procs.iter_mut() {
crate::inc_dec::visit_proc(arena, borrow_params, proc, key.1.clone());
}
}
@ -298,7 +279,6 @@ pub struct Procs<'a> {
pub runtime_errors: MutMap<Symbol, &'a str>,
pub externals_others_need: ExternalSpecializations,
pub externals_we_need: MutMap<ModuleId, ExternalSpecializations>,
pub passed_by_pointer: MutMap<(Symbol, Layout<'a>), Symbol>,
}
impl<'a> Default for Procs<'a> {
@ -311,7 +291,6 @@ impl<'a> Default for Procs<'a> {
runtime_errors: MutMap::default(),
externals_we_need: MutMap::default(),
externals_others_need: ExternalSpecializations::default(),
passed_by_pointer: MutMap::default(),
}
}
}
@ -356,14 +335,10 @@ impl<'a> Procs<'a> {
}
}
#[allow(clippy::type_complexity)]
pub fn get_specialized_procs_without_rc(
self,
arena: &'a Bump,
) -> (
MutMap<(Symbol, Layout<'a>), Proc<'a>>,
MutMap<(Symbol, Layout<'a>), Symbol>,
) {
) -> MutMap<(Symbol, Layout<'a>), Proc<'a>> {
let mut result = MutMap::with_capacity_and_hasher(self.specialized.len(), default_hasher());
for (key, in_prog_proc) in self.specialized.into_iter() {
@ -386,7 +361,7 @@ impl<'a> Procs<'a> {
}
}
(result, self.passed_by_pointer)
result
}
// TODO investigate make this an iterator?
@ -415,14 +390,10 @@ impl<'a> Procs<'a> {
}
}
let borrow_params = arena.alloc(crate::borrow::infer_borrow(
arena,
&result,
&self.passed_by_pointer,
));
let borrow_params = arena.alloc(crate::borrow::infer_borrow(arena, &result));
for (_, proc) in result.iter_mut() {
crate::inc_dec::visit_proc(arena, borrow_params, proc);
for (key, proc) in result.iter_mut() {
crate::inc_dec::visit_proc(arena, borrow_params, proc, key.1.clone());
}
result
@ -459,14 +430,10 @@ impl<'a> Procs<'a> {
}
}
let borrow_params = arena.alloc(crate::borrow::infer_borrow(
arena,
&result,
&self.passed_by_pointer,
));
let borrow_params = arena.alloc(crate::borrow::infer_borrow(arena, &result));
for (_, proc) in result.iter_mut() {
crate::inc_dec::visit_proc(arena, borrow_params, proc);
for (key, proc) in result.iter_mut() {
crate::inc_dec::visit_proc(arena, borrow_params, proc, key.1.clone());
}
(result, borrow_params)
@ -1935,6 +1902,7 @@ fn specialize_external<'a>(
closure_data_layout,
ret_layout: full_layout,
is_self_recursive: recursivity,
must_own_arguments: false,
host_exposed_layouts,
};
@ -1962,12 +1930,14 @@ fn specialize_external<'a>(
match tag_layout {
Layout::Struct(field_layouts) => {
// NOTE closure unions do not store the tag!
let field_layouts = &field_layouts[1..];
// TODO check for field_layouts.len() == 1 and do a rename in that case?
for (index, (symbol, _variable)) in captured.iter().enumerate()
for (mut index, (symbol, _variable)) in
captured.iter().enumerate()
{
// the field layouts do store the tag, but the tag value is
// not captured. So we drop the layout of the tag ID here
index += 1;
// TODO therefore should the wrapped here not be RecordOrSingleTagUnion?
let expr = Expr::AccessAtIndex {
index: index as _,
@ -2069,6 +2039,7 @@ fn specialize_external<'a>(
closure_data_layout,
ret_layout,
is_self_recursive: recursivity,
must_own_arguments: false,
host_exposed_layouts,
};
@ -2789,8 +2760,7 @@ pub fn with_hole<'a>(
);
let outer_symbol = env.unique_symbol();
stmt = store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt)
.unwrap();
stmt = store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt);
// convert the def body, store in outer_symbol
with_hole(
@ -3762,8 +3732,12 @@ pub fn with_hole<'a>(
.into_bump_slice();
// define the closure data, unless it's a basic unwrapped type already
match closure_layout.build_closure_data(name, symbols) {
Ok(expr) => {
match closure_layout.build_closure_data(name, &symbols) {
BuildClosureData::Alias(current) => {
// there is only one symbol captured, use that immediately
substitute_in_exprs(env.arena, &mut stmt, closure_data, current);
}
BuildClosureData::Struct(expr) => {
stmt = Stmt::Let(
closure_data,
expr,
@ -3771,9 +3745,40 @@ pub fn with_hole<'a>(
env.arena.alloc(stmt),
);
}
Err(current) => {
// there is only one symbol captured, use that immediately
substitute_in_exprs(env.arena, &mut stmt, closure_data, current);
BuildClosureData::Union {
tag_id,
tag_layout,
union_size,
tag_name,
} => {
let tag_id_symbol = env.unique_symbol();
let mut tag_symbols =
Vec::with_capacity_in(symbols.len() + 1, env.arena);
tag_symbols.push(tag_id_symbol);
tag_symbols.extend(symbols);
let expr1 = Expr::Literal(Literal::Int(tag_id as i64));
let expr2 = Expr::Tag {
tag_id,
tag_layout,
union_size,
tag_name,
arguments: tag_symbols.into_bump_slice(),
};
stmt = Stmt::Let(
closure_data,
expr2,
closure_data_layout.clone(),
env.arena.alloc(stmt),
);
stmt = Stmt::Let(
tag_id_symbol,
expr1,
Layout::Builtin(Builtin::Int64),
env.arena.alloc(stmt),
);
}
}
@ -4517,12 +4522,10 @@ pub fn from_can<'a>(
if let roc_can::expr::Expr::Var(outer_symbol) = def.loc_expr.value {
store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt)
.unwrap()
} else {
let outer_symbol = env.unique_symbol();
stmt =
store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt)
.unwrap();
store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt);
// convert the def body, store in outer_symbol
with_hole(
@ -4733,31 +4736,21 @@ fn from_can_when<'a>(
jump,
);
match store_pattern(env, procs, layout_cache, &pattern, cond_symbol, guard_stmt) {
Ok(new_guard_stmt) => (
pattern,
Guard::Guard {
id,
symbol,
stmt: new_guard_stmt,
},
branch_stmt,
),
Err(msg) => (
Pattern::Underscore,
Guard::NoGuard,
Stmt::RuntimeError(env.arena.alloc(msg)),
),
}
let new_guard_stmt =
store_pattern(env, procs, layout_cache, &pattern, cond_symbol, guard_stmt);
(
pattern,
Guard::Guard {
id,
symbol,
stmt: new_guard_stmt,
},
branch_stmt,
)
} else {
match store_pattern(env, procs, layout_cache, &pattern, cond_symbol, branch_stmt) {
Ok(new_branch_stmt) => (pattern, Guard::NoGuard, new_branch_stmt),
Err(msg) => (
Pattern::Underscore,
Guard::NoGuard,
Stmt::RuntimeError(env.arena.alloc(msg)),
),
}
let new_branch_stmt =
store_pattern(env, procs, layout_cache, &pattern, cond_symbol, branch_stmt);
(pattern, Guard::NoGuard, new_branch_stmt)
}
});
let mono_branches = Vec::from_iter_in(it, arena);
@ -5142,13 +5135,36 @@ fn substitute_in_expr<'a>(
#[allow(clippy::too_many_arguments)]
fn store_pattern<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
can_pat: &Pattern<'a>,
outer_symbol: Symbol,
stmt: Stmt<'a>,
) -> Stmt<'a> {
match store_pattern_help(env, procs, layout_cache, can_pat, outer_symbol, stmt) {
StorePattern::Productive(new) => new,
StorePattern::NotProductive(new) => new,
}
}
enum StorePattern<'a> {
/// we bound new symbols
Productive(Stmt<'a>),
/// no new symbols were bound in this pattern
NotProductive(Stmt<'a>),
}
/// It is crucial for correct RC insertion that we don't create dead variables!
#[allow(clippy::too_many_arguments)]
fn store_pattern_help<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
can_pat: &Pattern<'a>,
outer_symbol: Symbol,
mut stmt: Stmt<'a>,
) -> Result<Stmt<'a>, &'a str> {
) -> StorePattern<'a> {
use Pattern::*;
match can_pat {
@ -5157,12 +5173,15 @@ fn store_pattern<'a>(
}
Underscore => {
// do nothing
return StorePattern::NotProductive(stmt);
}
IntLiteral(_)
| FloatLiteral(_)
| EnumLiteral { .. }
| BitLiteral { .. }
| StrLiteral(_) => {}
| StrLiteral(_) => {
return StorePattern::NotProductive(stmt);
}
AppliedTag {
arguments, layout, ..
} => {
@ -5170,6 +5189,7 @@ fn store_pattern<'a>(
let write_tag = wrapped == Wrapped::MultiTagUnion;
let mut arg_layouts = Vec::with_capacity_in(arguments.len(), env.arena);
let mut is_productive = false;
if write_tag {
// add an element for the tag discriminant
@ -5200,6 +5220,7 @@ fn store_pattern<'a>(
Identifier(symbol) => {
// store immediately in the given symbol
stmt = Stmt::Let(*symbol, load, arg_layout.clone(), env.arena.alloc(stmt));
is_productive = true;
}
Underscore => {
// ignore
@ -5214,17 +5235,36 @@ fn store_pattern<'a>(
let symbol = env.unique_symbol();
// first recurse, continuing to unpack symbol
stmt = store_pattern(env, procs, layout_cache, argument, symbol, stmt)?;
// then store the symbol
stmt = Stmt::Let(symbol, load, arg_layout.clone(), env.arena.alloc(stmt));
match store_pattern_help(env, procs, layout_cache, argument, symbol, stmt) {
StorePattern::Productive(new) => {
is_productive = true;
stmt = new;
// only if we bind one of its (sub)fields to a used name should we
// extract the field
stmt = Stmt::Let(
symbol,
load,
arg_layout.clone(),
env.arena.alloc(stmt),
);
}
StorePattern::NotProductive(new) => {
// do nothing
stmt = new;
}
}
}
}
}
if !is_productive {
return StorePattern::NotProductive(stmt);
}
}
RecordDestructure(destructs, Layout::Struct(sorted_fields)) => {
let mut is_productive = false;
for (index, destruct) in destructs.iter().enumerate().rev() {
stmt = store_record_destruct(
match store_record_destruct(
env,
procs,
layout_cache,
@ -5233,7 +5273,19 @@ fn store_pattern<'a>(
outer_symbol,
sorted_fields,
stmt,
)?;
) {
StorePattern::Productive(new) => {
is_productive = true;
stmt = new;
}
StorePattern::NotProductive(new) => {
stmt = new;
}
}
}
if !is_productive {
return StorePattern::NotProductive(stmt);
}
}
@ -5242,7 +5294,7 @@ fn store_pattern<'a>(
}
}
Ok(stmt)
StorePattern::Productive(stmt)
}
#[allow(clippy::too_many_arguments)]
@ -5255,7 +5307,7 @@ fn store_record_destruct<'a>(
outer_symbol: Symbol,
sorted_fields: &'a [Layout<'a>],
mut stmt: Stmt<'a>,
) -> Result<Stmt<'a>, &'a str> {
) -> StorePattern<'a> {
use Pattern::*;
let wrapped = Wrapped::from_layout(&Layout::Struct(sorted_fields));
@ -5298,24 +5350,32 @@ fn store_record_destruct<'a>(
// { x, y: _ } -> ...
//
// internally. But `y` is never used, so we must make sure it't not stored/loaded.
return StorePattern::NotProductive(stmt);
}
IntLiteral(_)
| FloatLiteral(_)
| EnumLiteral { .. }
| BitLiteral { .. }
| StrLiteral(_) => {}
| StrLiteral(_) => {
return StorePattern::NotProductive(stmt);
}
_ => {
let symbol = env.unique_symbol();
stmt = store_pattern(env, procs, layout_cache, guard_pattern, symbol, stmt)?;
stmt = Stmt::Let(symbol, load, destruct.layout.clone(), env.arena.alloc(stmt));
match store_pattern_help(env, procs, layout_cache, guard_pattern, symbol, stmt) {
StorePattern::Productive(new) => {
stmt = new;
stmt =
Stmt::Let(symbol, load, destruct.layout.clone(), env.arena.alloc(stmt));
}
StorePattern::NotProductive(stmt) => return StorePattern::NotProductive(stmt),
}
}
},
}
Ok(stmt)
StorePattern::Productive(stmt)
}
/// We want to re-use symbols that are not function symbols
@ -5515,8 +5575,12 @@ fn reuse_function_symbol<'a>(
};
// define the closure data, unless it's a basic unwrapped type already
match closure_layout.build_closure_data(original, symbols) {
Ok(expr) => {
match closure_layout.build_closure_data(original, &symbols) {
BuildClosureData::Alias(current) => {
// there is only one symbol captured, use that immediately
substitute_in_exprs(env.arena, &mut stmt, closure_data, current);
}
BuildClosureData::Struct(expr) => {
stmt = Stmt::Let(
closure_data,
expr,
@ -5524,9 +5588,40 @@ fn reuse_function_symbol<'a>(
env.arena.alloc(stmt),
);
}
Err(current) => {
// there is only one symbol captured, use that immediately
substitute_in_exprs(env.arena, &mut stmt, closure_data, current);
BuildClosureData::Union {
tag_id,
tag_layout,
union_size,
tag_name,
} => {
let tag_id_symbol = env.unique_symbol();
let mut tag_symbols =
Vec::with_capacity_in(symbols.len() + 1, env.arena);
tag_symbols.push(tag_id_symbol);
tag_symbols.extend(symbols);
let expr1 = Expr::Literal(Literal::Int(tag_id as i64));
let expr2 = Expr::Tag {
tag_id,
tag_layout,
union_size,
tag_name,
arguments: tag_symbols.into_bump_slice(),
};
stmt = Stmt::Let(
closure_data,
expr2,
closure_data_layout.clone(),
env.arena.alloc(stmt),
);
stmt = Stmt::Let(
tag_id_symbol,
expr1,
Layout::Builtin(Builtin::Int64),
env.arena.alloc(stmt),
);
}
}
@ -5630,19 +5725,93 @@ where
}
fn call_by_pointer<'a>(
_env: &mut Env<'a, '_>,
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
symbol: Symbol,
layout: Layout<'a>,
) -> Expr<'a> {
// let other = env.unique_symbol();
let other = symbol;
// when we call a known function by-pointer, we must make sure we call a function that owns all
// its arguments (in an RC sense). we can't know this at this point, so we wrap such calls in
// a proc that we guarantee owns all its arguments. E.g. we turn
//
// foo = \x -> ...
//
// x = List.map [ ... ] foo
//
// into
//
// foo = \x -> ...
//
// @owns_all_arguments
// foo1 = \x -> foo x
//
// x = List.map [ ... ] foo1
procs
.passed_by_pointer
.insert((symbol, layout.clone()), other);
// TODO can we cache this `any`?
let is_specialized = procs.specialized.keys().any(|(s, _)| *s == symbol);
if env.is_imported_symbol(symbol) || procs.partial_procs.contains_key(&symbol) || is_specialized
{
match layout {
Layout::FunctionPointer(arg_layouts, ret_layout) => {
if arg_layouts.iter().any(|l| l.contains_refcounted()) {
let name = env.unique_symbol();
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);
Expr::FunctionPointer(other, layout)
for layout in arg_layouts {
let symbol = env.unique_symbol();
args.push((layout.clone(), symbol));
arg_symbols.push(symbol);
}
let args = args.into_bump_slice();
let call_symbol = env.unique_symbol();
let call_type = CallType::ByName {
name: symbol,
full_layout: layout.clone(),
ret_layout: ret_layout.clone(),
arg_layouts,
};
let call = Call {
call_type,
arguments: arg_symbols.into_bump_slice(),
};
let expr = Expr::Call(call);
let mut body = Stmt::Ret(call_symbol);
body = Stmt::Let(call_symbol, expr, ret_layout.clone(), env.arena.alloc(body));
let closure_data_layout = None;
let proc = Proc {
name,
args,
body,
closure_data_layout,
ret_layout: ret_layout.clone(),
is_self_recursive: SelfRecursive::NotSelfRecursive,
must_own_arguments: true,
host_exposed_layouts: HostExposedLayouts::NotHostExposed,
};
procs
.specialized
.insert((name, layout.clone()), InProgressProc::Done(proc));
Expr::FunctionPointer(name, layout)
} else {
// if none of the arguments is refcounted, then owning the arguments has no
// meaning
Expr::FunctionPointer(symbol, layout)
}
}
_ => {
// e.g. Num.maxInt or other constants
Expr::FunctionPointer(symbol, layout)
}
}
} else {
Expr::FunctionPointer(symbol, layout)
}
}
fn add_needed_external<'a>(
@ -5770,7 +5939,8 @@ fn call_by_name<'a>(
debug_assert_eq!(
arg_layouts.len(),
field_symbols.len(),
"see call_by_name for background (scroll down a bit)"
"see call_by_name for background (scroll down a bit), function is {:?}",
proc_name,
);
let call = self::Call {
@ -5821,7 +5991,8 @@ fn call_by_name<'a>(
debug_assert_eq!(
arg_layouts.len(),
field_symbols.len(),
"see call_by_name for background (scroll down a bit)"
"see call_by_name for background (scroll down a bit), function is {:?}",
proc_name,
);
let call = self::Call {
@ -6343,8 +6514,10 @@ fn from_can_pattern_help<'a>(
debug_assert_eq!(
arguments.len(),
argument_layouts[1..].len(),
"{:?}",
tag_name
"The {:?} tag got {} arguments, but its layout expects {}!",
tag_name,
arguments.len(),
argument_layouts[1..].len(),
);
let it = argument_layouts[1..].iter();

View file

@ -79,7 +79,7 @@ impl<'a> UnionLayout<'a> {
let tags_doc = tags.iter().map(|fields| {
alloc.text("C ").append(alloc.intersperse(
fields.iter().map(|x| x.to_doc(alloc, Parens::InTypeParam)),
", ",
" ",
))
});
@ -316,16 +316,16 @@ impl<'a> ClosureLayout<'a> {
&self,
original: Symbol,
symbols: &'a [Symbol],
) -> Result<crate::ir::Expr<'a>, Symbol> {
) -> BuildClosureData<'a> {
use crate::ir::Expr;
match self.layout {
Layout::Struct(fields) if fields.len() == 1 => Err(symbols[0]),
Layout::Struct(fields) if fields.len() == 1 => BuildClosureData::Alias(symbols[0]),
Layout::Struct(fields) => {
debug_assert!(fields.len() > 1);
debug_assert_eq!(fields.len(), symbols.len());
Ok(Expr::Struct(symbols))
BuildClosureData::Struct(Expr::Struct(symbols))
}
Layout::Union(UnionLayout::NonRecursive(tags)) => {
// NOTE it's very important that this Union consists of Closure tags
@ -337,20 +337,17 @@ impl<'a> ClosureLayout<'a> {
.position(|(tn, _)| *tn == TagName::Closure(original))
.unwrap() as _;
let expr = Expr::Tag {
BuildClosureData::Union {
tag_layout: Layout::Union(UnionLayout::NonRecursive(tags)),
tag_name: TagName::Closure(original),
tag_id,
union_size: tags.len() as u8,
arguments: symbols,
};
Ok(expr)
}
}
Layout::PhantomEmptyStruct => {
debug_assert_eq!(symbols.len(), 1);
Ok(Expr::Struct(&[]))
BuildClosureData::Struct(Expr::Struct(&[]))
}
_ => {
@ -362,12 +359,23 @@ impl<'a> ClosureLayout<'a> {
&self.layout
);
Err(symbols[0])
BuildClosureData::Alias(symbols[0])
}
}
}
}
pub enum BuildClosureData<'a> {
Alias(Symbol),
Struct(crate::ir::Expr<'a>),
Union {
tag_layout: Layout<'a>,
tag_name: TagName,
tag_id: u8,
union_size: u8,
},
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Copy)]
pub enum MemoryMode {
Unique,
@ -872,7 +880,7 @@ impl<'a> Builtin<'a> {
/// Number of machine words in an empty one of these
pub const STR_WORDS: u32 = 2;
pub const DICT_WORDS: u32 = 6;
pub const DICT_WORDS: u32 = 3;
pub const SET_WORDS: u32 = Builtin::DICT_WORDS; // Set is an alias for Dict with {} for value
pub const LIST_WORDS: u32 = 2;

View file

@ -646,13 +646,13 @@ mod test_mono {
let Test.4 = lowlevel DictEmpty ;
ret Test.4;
procedure Dict.6 (#Attr.2):
procedure Dict.8 (#Attr.2):
let Test.3 = lowlevel DictSize #Attr.2;
ret Test.3;
procedure Test.0 ():
let Test.2 = FunctionPointer Dict.2;
let Test.1 = CallByName Dict.6 Test.2;
let Test.1 = CallByName Dict.8 Test.2;
ret Test.1;
"#
),
@ -1969,15 +1969,14 @@ mod test_mono {
let Test.7 = Index 1 Test.2;
let Test.8 = 0i64;
let Test.9 = Index 0 Test.7;
dec Test.7;
decref Test.2;
let Test.10 = lowlevel Eq Test.8 Test.9;
if Test.10 then
let Test.4 = Index 1 Test.2;
let Test.3 = 1i64;
decref Test.2;
ret Test.3;
else
let Test.5 = 0i64;
dec Test.2;
ret Test.5;
else
let Test.6 = 0i64;

View file

@ -207,6 +207,33 @@ where
)
}
pub fn space0_after_e<'a, P, S, E>(
parser: P,
min_indent: u16,
space_problem: fn(BadInputError, Row, Col) -> E,
indent_problem: fn(Row, Col) -> E,
) -> impl Parser<'a, Located<S>, E>
where
S: Spaceable<'a>,
S: 'a,
P: Parser<'a, Located<S>, E>,
P: 'a,
E: 'a,
{
parser::map_with_arena(
and!(parser, space0_e(min_indent, space_problem, indent_problem)),
|arena: &'a Bump, (loc_expr, space_list): (Located<S>, &'a [CommentOrNewline<'a>])| {
if space_list.is_empty() {
loc_expr
} else {
arena
.alloc(loc_expr.value)
.with_spaces_after(space_list, loc_expr.region)
}
},
)
}
/// Parses the given expression with 1 or more (spaces/comments/newlines) before it.
/// Returns a Located<Expr> where the location is around the Expr, ignoring the spaces.
/// The Expr will be wrapped in a SpaceBefore if there were any newlines or comments found.

View file

@ -2,17 +2,19 @@ use crate::ast::{
AssignedField, Attempting, CommentOrNewline, Def, Expr, Pattern, Spaceable, TypeAnnotation,
};
use crate::blankspace::{
line_comment, space0, space0_after, space0_around, space0_before, space1, space1_around,
space1_before, spaces_exactly,
line_comment, space0, space0_after, space0_after_e, space0_around, space0_around_e,
space0_before, space0_before_e, space0_e, space1, space1_around, space1_before, spaces_exactly,
};
use crate::ident::{global_tag_or_ident, ident, lowercase_ident, Ident};
use crate::keyword;
use crate::number_literal::number_literal;
use crate::parser::{
self, allocated, and_then_with_indent_level, ascii_char, ascii_string, attempt, backtrackable,
fail, map, newline_char, not, not_followed_by, optional, sep_by1, then, unexpected,
unexpected_eof, Either, ParseResult, Parser, State, SyntaxError,
fail, map, newline_char, not, not_followed_by, optional, sep_by1, specialize, specialize_ref,
then, unexpected, unexpected_eof, word1, word2, EExpr, Either, ParseResult, Parser, State,
SyntaxError, When,
};
use crate::pattern::loc_closure_param;
use crate::type_annotation;
use bumpalo::collections::string::String;
use bumpalo::collections::Vec;
@ -243,7 +245,10 @@ fn parse_expr<'a>(
/// If the given Expr would parse the same way as a valid Pattern, convert it.
/// Example: (foo) could be either an Expr::Var("foo") or Pattern::Identifier("foo")
fn expr_to_pattern<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<'a>, SyntaxError<'a>> {
pub fn expr_to_pattern<'a>(
arena: &'a Bump,
expr: &Expr<'a>,
) -> Result<Pattern<'a>, SyntaxError<'a>> {
match expr {
Expr::Var { module_name, ident } => {
if module_name.is_empty() {
@ -1008,343 +1013,40 @@ fn closure<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>, SyntaxError<'a>> {
)
}
fn loc_closure_param<'a>(
min_indent: u16,
) -> impl Parser<'a, Located<Pattern<'a>>, SyntaxError<'a>> {
move |arena, state| parse_closure_param(arena, state, min_indent)
}
fn parse_closure_param<'a>(
arena: &'a Bump,
state: State<'a>,
min_indent: u16,
) -> ParseResult<'a, Located<Pattern<'a>>, SyntaxError<'a>> {
one_of!(
// An ident is the most common param, e.g. \foo -> ...
loc_ident_pattern(min_indent, true),
// Underscore is also common, e.g. \_ -> ...
loc!(underscore_pattern()),
// You can destructure records in params, e.g. \{ x, y } -> ...
loc!(record_destructure(min_indent)),
// If you wrap it in parens, you can match any arbitrary pattern at all.
// e.g. \User.UserId userId -> ...
between!(
ascii_char(b'('),
space0_around(loc_pattern(min_indent), min_indent),
ascii_char(b')')
)
)
.parse(arena, state)
}
fn loc_pattern<'a>(min_indent: u16) -> impl Parser<'a, Located<Pattern<'a>>, SyntaxError<'a>> {
skip_first!(
// If this is a reserved keyword ("if", "then", "case, "when"), then
// it is not a pattern!
not(reserved_keyword()),
one_of!(
loc_parenthetical_pattern(min_indent),
loc!(underscore_pattern()),
loc_ident_pattern(min_indent, true),
loc!(record_destructure(min_indent)),
loc!(string_pattern()),
loc!(number_pattern())
)
)
}
fn loc_tag_pattern_args<'a>(
min_indent: u16,
) -> impl Parser<'a, Vec<'a, Located<Pattern<'a>>>, SyntaxError<'a>> {
zero_or_more!(space1_before(loc_tag_pattern_arg(min_indent), min_indent))
}
fn loc_tag_pattern_arg<'a>(
min_indent: u16,
) -> impl Parser<'a, Located<Pattern<'a>>, SyntaxError<'a>> {
skip_first!(
// If this is a reserved keyword ("if", "then", "case, "when"), then
// it is not a function argument!
not(reserved_keyword()),
// Don't parse operators, because they have a higher precedence than function application.
// If we encounter one, we're done parsing function args!
move |arena, state| loc_parse_tag_pattern_arg(min_indent, arena, state)
)
}
fn loc_parse_tag_pattern_arg<'a>(
min_indent: u16,
arena: &'a Bump,
state: State<'a>,
) -> ParseResult<'a, Located<Pattern<'a>>, SyntaxError<'a>> {
one_of!(
loc_parenthetical_pattern(min_indent),
loc!(underscore_pattern()),
// Make sure `Foo Bar 1` is parsed as `Foo (Bar) 1`, and not `Foo (Bar 1)`
loc_ident_pattern(min_indent, false),
loc!(record_destructure(min_indent)),
loc!(string_pattern()),
loc!(number_pattern())
)
.parse(arena, state)
}
fn loc_parenthetical_pattern<'a>(
min_indent: u16,
) -> impl Parser<'a, Located<Pattern<'a>>, SyntaxError<'a>> {
between!(
ascii_char(b'('),
space0_around(
move |arena, state| loc_pattern(min_indent).parse(arena, state),
min_indent,
),
ascii_char(b')')
)
}
fn number_pattern<'a>() -> impl Parser<'a, Pattern<'a>, SyntaxError<'a>> {
map_with_arena!(number_literal(), |arena, expr| {
expr_to_pattern(arena, &expr).unwrap()
})
}
fn string_pattern<'a>() -> impl Parser<'a, Pattern<'a>, SyntaxError<'a>> {
map!(crate::string_literal::parse(), Pattern::StrLiteral)
}
fn underscore_pattern<'a>() -> impl Parser<'a, Pattern<'a>, SyntaxError<'a>> {
move |arena: &'a Bump, state: State<'a>| {
let (_, _, next_state) = ascii_char(b'_').parse(arena, state)?;
let (_, output, final_state) = optional(lowercase_ident()).parse(arena, next_state)?;
match output {
Some(name) => Ok((MadeProgress, Pattern::Underscore(name), final_state)),
None => Ok((MadeProgress, Pattern::Underscore(&""), final_state)),
}
}
}
fn record_destructure<'a>(min_indent: u16) -> impl Parser<'a, Pattern<'a>, SyntaxError<'a>> {
then(
collection!(
ascii_char(b'{'),
move |arena: &'a bumpalo::Bump,
state: crate::parser::State<'a>|
-> crate::parser::ParseResult<
'a,
Located<crate::ast::Pattern<'a>>,
SyntaxError,
> {
use crate::blankspace::{space0, space0_before};
use crate::ident::lowercase_ident;
use crate::parser::Either::*;
use roc_region::all::Region;
// You must have a field name, e.g. "email"
let (p1, loc_label, state) = loc!(lowercase_ident()).parse(arena, state)?;
let (p2, spaces, state) = space0(min_indent).parse(arena, state)?;
// Having a value is optional; both `{ email }` and `{ email: blah }` work.
// (This is true in both literals and types.)
let (p3, opt_loc_val, state) = crate::parser::optional(either!(
skip_first!(
ascii_char(b':'),
space0_before(loc_pattern(min_indent), min_indent)
),
skip_first!(
ascii_char(b'?'),
space0_before(loc!(expr(min_indent)), min_indent)
)
))
.parse(arena, state)?;
let answer = match opt_loc_val {
Some(either) => match either {
First(loc_val) => Located {
region: Region::span_across(&loc_label.region, &loc_val.region),
value: Pattern::RequiredField(loc_label.value, arena.alloc(loc_val)),
},
Second(loc_val) => Located {
region: Region::span_across(&loc_label.region, &loc_val.region),
value: Pattern::OptionalField(loc_label.value, arena.alloc(loc_val)),
},
},
// If no value was provided, record it as a Var.
// Canonicalize will know what to do with a Var later.
None => {
if !spaces.is_empty() {
Located {
region: loc_label.region,
value: Pattern::SpaceAfter(
arena.alloc(Pattern::Identifier(loc_label.value)),
spaces,
),
}
} else {
Located {
region: loc_label.region,
value: Pattern::Identifier(loc_label.value),
}
}
}
};
let progress = p1.or(p2).or(p3);
debug_assert_eq!(progress, MadeProgress);
Ok((MadeProgress, answer, state))
},
ascii_char(b','),
ascii_char(b'}'),
min_indent
),
move |_arena, state, progress, loc_patterns| {
Ok((
progress,
Pattern::RecordDestructure(loc_patterns.into_bump_slice()),
state,
))
},
)
}
fn loc_ident_pattern<'a>(
min_indent: u16,
can_have_arguments: bool,
) -> impl Parser<'a, Located<Pattern<'a>>, SyntaxError<'a>> {
move |arena: &'a Bump, state: State<'a>| {
let (_, loc_ident, state) = loc!(ident()).parse(arena, state)?;
match loc_ident.value {
Ident::GlobalTag(tag) => {
let loc_tag = Located {
region: loc_ident.region,
value: Pattern::GlobalTag(tag),
};
// Make sure `Foo Bar 1` is parsed as `Foo (Bar) 1`, and not `Foo (Bar 1)`
if can_have_arguments {
let (_, loc_args, state) =
loc_tag_pattern_args(min_indent).parse(arena, state)?;
if loc_args.is_empty() {
Ok((MadeProgress, loc_tag, state))
} else {
let region = Region::across_all(
std::iter::once(&loc_ident.region)
.chain(loc_args.iter().map(|loc_arg| &loc_arg.region)),
);
let value =
Pattern::Apply(&*arena.alloc(loc_tag), loc_args.into_bump_slice());
Ok((MadeProgress, Located { region, value }, state))
}
} else {
Ok((MadeProgress, loc_tag, state))
}
}
Ident::PrivateTag(tag) => {
let loc_tag = Located {
region: loc_ident.region,
value: Pattern::PrivateTag(tag),
};
// Make sure `Foo Bar 1` is parsed as `Foo (Bar) 1`, and not `Foo (Bar 1)`
if can_have_arguments {
let (_, loc_args, state) =
loc_tag_pattern_args(min_indent).parse(arena, state)?;
if loc_args.is_empty() {
Ok((MadeProgress, loc_tag, state))
} else {
let region = Region::across_all(
std::iter::once(&loc_ident.region)
.chain(loc_args.iter().map(|loc_arg| &loc_arg.region)),
);
let value =
Pattern::Apply(&*arena.alloc(loc_tag), loc_args.into_bump_slice());
Ok((MadeProgress, Located { region, value }, state))
}
} else {
Ok((MadeProgress, loc_tag, state))
}
}
Ident::Access { module_name, parts } => {
// Plain identifiers (e.g. `foo`) are allowed in patterns, but
// more complex ones (e.g. `Foo.bar` or `foo.bar.baz`) are not.
if module_name.is_empty() && parts.len() == 1 {
Ok((
MadeProgress,
Located {
region: loc_ident.region,
value: Pattern::Identifier(parts[0]),
},
state,
))
} else {
let malformed_str = if module_name.is_empty() {
parts.join(".")
} else {
format!("{}.{}", module_name, parts.join("."))
};
Ok((
MadeProgress,
Located {
region: loc_ident.region,
value: Pattern::Malformed(
String::from_str_in(&malformed_str, &arena).into_bump_str(),
),
},
state,
))
}
}
Ident::AccessorFunction(string) => Ok((
MadeProgress,
Located {
region: loc_ident.region,
value: Pattern::Malformed(string),
},
state,
)),
Ident::Malformed(malformed) => {
debug_assert!(!malformed.is_empty());
Err((MadeProgress, SyntaxError::InvalidPattern, state))
}
}
}
}
mod when {
use super::*;
use crate::ast::WhenBranch;
/// Parser for when expressions.
pub fn expr<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>, SyntaxError<'a>> {
specialize(
|e, r, c| SyntaxError::Expr(EExpr::When(e, r, c)),
expr_help(min_indent),
)
}
pub fn expr_help<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>, When<'a>> {
then(
and!(
when_with_indent(min_indent),
attempt!(
Attempting::WhenCondition,
skip_second!(
space1_around(
loc!(move |arena, state| parse_expr(min_indent, arena, state)),
min_indent,
),
parser::keyword(keyword::IS, min_indent)
)
when_with_indent(),
skip_second!(
space0_around_e(
loc!(specialize_ref(
When::Syntax,
move |arena, state| parse_expr(min_indent, arena, state)
)),
min_indent,
When::Space,
When::IndentCondition
),
parser::keyword_e(keyword::IS, When::Is)
)
),
move |arena, state, progress, (case_indent, loc_condition)| {
if case_indent < min_indent {
return Err((
progress,
SyntaxError::NotYetImplemented(
"TODO case wasn't indented enough".to_string(),
),
// TODO maybe pass case_indent here?
When::PatternAlignment(5, state.line, state.column),
state,
));
}
@ -1352,8 +1054,7 @@ mod when {
// Everything in the branches must be indented at least as much as the case itself.
let min_indent = case_indent;
let (p1, branches, state) =
attempt!(Attempting::WhenBranch, branches(min_indent)).parse(arena, state)?;
let (p1, branches, state) = branches(min_indent).parse(arena, state)?;
Ok((
progress.or(p1),
@ -1365,18 +1066,15 @@ mod when {
}
/// Parsing when with indentation.
fn when_with_indent<'a>(min_indent: u16) -> impl Parser<'a, u16, SyntaxError<'a>> {
fn when_with_indent<'a>() -> impl Parser<'a, u16, When<'a>> {
move |arena, state: State<'a>| {
parser::keyword(keyword::WHEN, min_indent)
parser::keyword_e(keyword::WHEN, When::When)
.parse(arena, state)
.map(|(progress, (), state)| (progress, state.indent_col, state))
}
}
/// Parsing branches of when conditional.
fn branches<'a>(
min_indent: u16,
) -> impl Parser<'a, Vec<'a, &'a WhenBranch<'a>>, SyntaxError<'a>> {
fn branches<'a>(min_indent: u16) -> impl Parser<'a, Vec<'a, &'a WhenBranch<'a>>, When<'a>> {
move |arena, state| {
let mut branches: Vec<'a, &'a WhenBranch<'a>> = Vec::with_capacity_in(2, arena);
@ -1405,17 +1103,13 @@ mod when {
then(
branch_alternatives(min_indent),
move |_arena, state, _, (loc_patterns, loc_guard)| {
if alternatives_indented_correctly(&loc_patterns, original_indent) {
Ok((MadeProgress, (loc_patterns, loc_guard), state))
} else {
Err((
MadeProgress,
SyntaxError::NotYetImplemented(
"TODO additional branch didn't have same indentation as first branch".to_string(),
),
match alternatives_indented_correctly(&loc_patterns, original_indent) {
Ok(()) => Ok((MadeProgress, (loc_patterns, loc_guard), state)),
Err(indent) => Err((
MadeProgress,
When::PatternAlignment(indent, state.line, state.column),
state,
))
)),
}
},
),
@ -1438,7 +1132,10 @@ mod when {
branches.push(arena.alloc(next_output));
}
Err((_, _, old_state)) => {
Err((MadeProgress, problem, old_state)) => {
return Err((MadeProgress, problem, old_state));
}
Err((NoProgress, _, old_state)) => {
state = old_state;
break;
@ -1453,21 +1150,51 @@ mod when {
/// Parsing alternative patterns in when branches.
fn branch_alternatives<'a>(
min_indent: u16,
) -> impl Parser<'a, (Vec<'a, Located<Pattern<'a>>>, Option<Located<Expr<'a>>>), SyntaxError<'a>>
{
) -> impl Parser<'a, (Vec<'a, Located<Pattern<'a>>>, Option<Located<Expr<'a>>>), When<'a>> {
and!(
sep_by1(
ascii_char(b'|'),
space0_around(loc_pattern(min_indent), min_indent),
),
optional(skip_first!(
parser::keyword(keyword::IF, min_indent),
// TODO we should require space before the expression but not after
space1_around(
loc!(move |arena, state| parse_expr(min_indent, arena, state)),
min_indent
sep_by1(word1(b'|', When::Bar), |arena, state| {
let (_, spaces, state) =
backtrackable(space0_e(min_indent, When::Space, When::IndentPattern))
.parse(arena, state)?;
let (_, loc_pattern, state) = space0_after_e(
specialize(When::Pattern, crate::pattern::loc_pattern_help(min_indent)),
min_indent,
When::Space,
When::IndentPattern,
)
))
.parse(arena, state)?;
Ok((
MadeProgress,
if spaces.is_empty() {
loc_pattern
} else {
arena
.alloc(loc_pattern.value)
.with_spaces_before(spaces, loc_pattern.region)
},
state,
))
}),
one_of![
map!(
skip_first!(
parser::keyword_e(keyword::IF, When::IfToken),
// TODO we should require space before the expression but not after
space0_around_e(
loc!(specialize_ref(When::IfGuard, move |arena, state| {
parse_expr(min_indent, arena, state)
})),
min_indent,
When::Space,
When::IndentIfGuard,
)
),
Some
),
|_, s| Ok((NoProgress, None, s))
]
)
}
@ -1475,22 +1202,33 @@ mod when {
fn alternatives_indented_correctly<'a>(
loc_patterns: &'a Vec<'a, Located<Pattern<'a>>>,
original_indent: u16,
) -> bool {
) -> Result<(), u16> {
let (first, rest) = loc_patterns.split_first().unwrap();
let first_indented_correctly = first.region.start_col == original_indent;
let rest_indented_correctly = rest
.iter()
.all(|when_pattern| when_pattern.region.start_col >= original_indent);
first_indented_correctly && rest_indented_correctly
if first_indented_correctly {
for when_pattern in rest.iter() {
if when_pattern.region.start_col < original_indent {
return Err(original_indent - when_pattern.region.start_col);
}
}
Ok(())
} else {
Err(original_indent - first.region.start_col)
}
}
/// Parsing the righthandside of a branch in a when conditional.
fn branch_result<'a>(indent: u16) -> impl Parser<'a, Located<Expr<'a>>, SyntaxError<'a>> {
fn branch_result<'a>(indent: u16) -> impl Parser<'a, Located<Expr<'a>>, When<'a>> {
skip_first!(
ascii_string("->"),
space0_before(
loc!(move |arena, state| parse_expr(indent, arena, state)),
word2(b'-', b'>', When::Arrow),
space0_before_e(
specialize_ref(
When::Syntax,
loc!(move |arena, state| parse_expr(indent, arena, state))
),
indent,
When::Space,
When::IndentBranch,
)
)
}

View file

@ -321,6 +321,8 @@ pub enum SyntaxError<'a> {
NotYetImplemented(String),
TODO,
Type(Type<'a>),
Pattern(EPattern<'a>),
Expr(EExpr<'a>),
Space(BadInputError),
}
@ -368,6 +370,95 @@ impl<'a> SyntaxError<'a> {
pub type Row = u32;
pub type Col = u16;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EExpr<'a> {
// Record(PRecord<'a>, Row, Col),
Start(Row, Col),
End(Row, Col),
Space(BadInputError, Row, Col),
When(When<'a>, Row, Col),
// EInParens(PInParens<'a>, Row, Col),
IndentStart(Row, Col),
IndentEnd(Row, Col),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum When<'a> {
Space(BadInputError, Row, Col),
When(Row, Col),
Is(Row, Col),
Pattern(EPattern<'a>, Row, Col),
Arrow(Row, Col),
Bar(Row, Col),
IfToken(Row, Col),
// TODO make EEXpr
IfGuard(&'a SyntaxError<'a>, Row, Col),
Condition(&'a EExpr<'a>, Row, Col),
Branch(&'a EExpr<'a>, Row, Col),
Syntax(&'a SyntaxError<'a>, Row, Col),
IndentIs(Row, Col),
IndentCondition(Row, Col),
IndentPattern(Row, Col),
IndentArrow(Row, Col),
IndentBranch(Row, Col),
IndentIfGuard(Row, Col),
PatternAlignment(u16, Row, Col),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EPattern<'a> {
Record(PRecord<'a>, Row, Col),
Underscore(Row, Col),
Start(Row, Col),
End(Row, Col),
Space(BadInputError, Row, Col),
PInParens(PInParens<'a>, Row, Col),
IndentStart(Row, Col),
IndentEnd(Row, Col),
AsIndentStart(Row, Col),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PRecord<'a> {
End(Row, Col),
Open(Row, Col),
Field(Row, Col),
Colon(Row, Col),
Optional(Row, Col),
Pattern(&'a EPattern<'a>, Row, Col),
// TODO remove
Syntax(&'a SyntaxError<'a>, Row, Col),
Space(BadInputError, Row, Col),
IndentOpen(Row, Col),
IndentColon(Row, Col),
IndentOptional(Row, Col),
IndentEnd(Row, Col),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PInParens<'a> {
End(Row, Col),
Open(Row, Col),
///
// TODO remove
Syntax(&'a SyntaxError<'a>, Row, Col),
///
Space(BadInputError, Row, Col),
///
IndentOpen(Row, Col),
IndentEnd(Row, Col),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Type<'a> {
TRecord(TRecord<'a>, Row, Col),
@ -882,16 +973,17 @@ pub fn keyword<'a>(
}
}
pub fn keyword_e<'a, E>(keyword: &'static str, if_error: E) -> impl Parser<'a, (), E>
pub fn keyword_e<'a, ToError, E>(keyword: &'static str, if_error: ToError) -> impl Parser<'a, (), E>
where
E: 'a + Clone,
ToError: Fn(Row, Col) -> E,
E: 'a,
{
move |arena, state: State<'a>| {
let initial_state = state.clone();
// first parse the keyword characters
let (_, _, after_keyword_state) = ascii_string(keyword)
.parse(arena, state)
.map_err(|(_, _, state)| (NoProgress, if_error.clone(), state))?;
.map_err(|(_, _, state)| (NoProgress, if_error(state.line, state.column), state))?;
// then we must have at least one space character
// TODO this is potentially wasteful if there are a lot of spaces
@ -905,7 +997,11 @@ where
// this is not a keyword, maybe it's `whence` or `iffy`
// anyway, make no progress and return the initial state
// so we can try something else
Err((NoProgress, if_error.clone(), initial_state))
Err((
NoProgress,
if_error(initial_state.line, initial_state.column),
initial_state,
))
}
}
}

View file

@ -1,3 +1,17 @@
use crate::ast::Pattern;
use crate::blankspace::{space0_around_e, space0_before_e, space0_e};
use crate::ident::{ident, lowercase_ident, Ident};
use crate::number_literal::number_literal;
use crate::parser::Progress::{self, *};
use crate::parser::{
backtrackable, optional, specialize, specialize_ref, word1, BadInputError, EPattern, PInParens,
PRecord, ParseResult, Parser, State, SyntaxError,
};
use bumpalo::collections::string::String;
use bumpalo::collections::Vec;
use bumpalo::Bump;
use roc_region::all::{Located, Region};
/// Different patterns are supported in different circumstances.
/// For example, when branches can pattern match on number literals, but
/// assignments and function args can't. Underscore is supported in function
@ -9,3 +23,421 @@ pub enum PatternType {
FunctionArg,
WhenBranch,
}
pub fn loc_closure_param<'a>(
min_indent: u16,
) -> impl Parser<'a, Located<Pattern<'a>>, SyntaxError<'a>> {
specialize(
|e, _, _| SyntaxError::Pattern(e),
move |arena, state| parse_closure_param(arena, state, min_indent),
)
}
fn parse_closure_param<'a>(
arena: &'a Bump,
state: State<'a>,
min_indent: u16,
) -> ParseResult<'a, Located<Pattern<'a>>, EPattern<'a>> {
one_of!(
// An ident is the most common param, e.g. \foo -> ...
loc_ident_pattern_help(min_indent, true),
// Underscore is also common, e.g. \_ -> ...
loc!(underscore_pattern_help()),
// You can destructure records in params, e.g. \{ x, y } -> ...
loc!(specialize(
EPattern::Record,
crate::pattern::record_pattern_help(min_indent)
)),
// If you wrap it in parens, you can match any arbitrary pattern at all.
// e.g. \User.UserId userId -> ...
specialize(EPattern::PInParens, loc_pattern_in_parens_help(min_indent))
)
.parse(arena, state)
}
pub fn loc_pattern<'a>(min_indent: u16) -> impl Parser<'a, Located<Pattern<'a>>, SyntaxError<'a>> {
specialize(
|e, _, _| SyntaxError::Pattern(e),
loc_pattern_help(min_indent),
)
}
pub fn loc_pattern_help<'a>(
min_indent: u16,
) -> impl Parser<'a, Located<Pattern<'a>>, EPattern<'a>> {
one_of!(
specialize(EPattern::PInParens, loc_pattern_in_parens_help(min_indent)),
loc!(underscore_pattern_help()),
loc_ident_pattern_help(min_indent, true),
loc!(specialize(
EPattern::Record,
crate::pattern::record_pattern_help(min_indent)
)),
loc!(string_pattern_help()),
loc!(number_pattern_help())
)
}
fn loc_tag_pattern_args_help<'a>(
min_indent: u16,
) -> impl Parser<'a, Vec<'a, Located<Pattern<'a>>>, EPattern<'a>> {
zero_or_more!(loc_tag_pattern_arg(min_indent))
}
fn loc_tag_pattern_arg<'a>(min_indent: u16) -> impl Parser<'a, Located<Pattern<'a>>, EPattern<'a>> {
// Don't parse operators, because they have a higher precedence than function application.
// If we encounter one, we're done parsing function args!
move |arena, state| {
let (_, spaces, state) =
backtrackable(space0_e(min_indent, EPattern::Space, EPattern::IndentStart))
.parse(arena, state)?;
let (_, loc_pat, state) = loc_parse_tag_pattern_arg(min_indent, arena, state)?;
let Located { region, value } = loc_pat;
Ok((
MadeProgress,
if spaces.is_empty() {
Located::at(region, value)
} else {
Located::at(region, Pattern::SpaceBefore(arena.alloc(value), spaces))
},
state,
))
}
}
fn loc_parse_tag_pattern_arg<'a>(
min_indent: u16,
arena: &'a Bump,
state: State<'a>,
) -> ParseResult<'a, Located<Pattern<'a>>, EPattern<'a>> {
one_of!(
specialize(EPattern::PInParens, loc_pattern_in_parens_help(min_indent)),
loc!(underscore_pattern_help()),
// Make sure `Foo Bar 1` is parsed as `Foo (Bar) 1`, and not `Foo (Bar 1)`
loc_ident_pattern_help(min_indent, false),
loc!(specialize(
EPattern::Record,
crate::pattern::record_pattern_help(min_indent)
)),
loc!(string_pattern_help()),
loc!(number_pattern_help())
)
.parse(arena, state)
}
fn loc_pattern_in_parens_help<'a>(
min_indent: u16,
) -> impl Parser<'a, Located<Pattern<'a>>, PInParens<'a>> {
between!(
word1(b'(', PInParens::Open),
space0_around_e(
move |arena, state| specialize_ref(PInParens::Syntax, loc_pattern(min_indent))
.parse(arena, state),
min_indent,
PInParens::Space,
PInParens::IndentEnd,
),
word1(b')', PInParens::End)
)
}
fn number_pattern_help<'a>() -> impl Parser<'a, Pattern<'a>, EPattern<'a>> {
specialize(
|_, r, c| EPattern::Start(r, c),
map_with_arena!(number_literal(), |arena, expr| {
crate::expr::expr_to_pattern(arena, &expr).unwrap()
}),
)
}
fn string_pattern_help<'a>() -> impl Parser<'a, Pattern<'a>, EPattern<'a>> {
specialize(
|_, r, c| EPattern::Start(r, c),
map!(crate::string_literal::parse(), Pattern::StrLiteral),
)
}
fn loc_ident_pattern_help<'a>(
min_indent: u16,
can_have_arguments: bool,
) -> impl Parser<'a, Located<Pattern<'a>>, EPattern<'a>> {
move |arena: &'a Bump, state: State<'a>| {
let original_state = state.clone();
let (_, loc_ident, state) =
specialize(|_, r, c| EPattern::Start(r, c), loc!(ident())).parse(arena, state)?;
match loc_ident.value {
Ident::GlobalTag(tag) => {
let loc_tag = Located {
region: loc_ident.region,
value: Pattern::GlobalTag(tag),
};
// Make sure `Foo Bar 1` is parsed as `Foo (Bar) 1`, and not `Foo (Bar 1)`
if can_have_arguments {
let (_, loc_args, state) =
loc_tag_pattern_args_help(min_indent).parse(arena, state)?;
if loc_args.is_empty() {
Ok((MadeProgress, loc_tag, state))
} else {
let region = Region::across_all(
std::iter::once(&loc_ident.region)
.chain(loc_args.iter().map(|loc_arg| &loc_arg.region)),
);
let value =
Pattern::Apply(&*arena.alloc(loc_tag), loc_args.into_bump_slice());
Ok((MadeProgress, Located { region, value }, state))
}
} else {
Ok((MadeProgress, loc_tag, state))
}
}
Ident::PrivateTag(tag) => {
let loc_tag = Located {
region: loc_ident.region,
value: Pattern::PrivateTag(tag),
};
// Make sure `Foo Bar 1` is parsed as `Foo (Bar) 1`, and not `Foo (Bar 1)`
if can_have_arguments {
let (_, loc_args, state) =
loc_tag_pattern_args_help(min_indent).parse(arena, state)?;
if loc_args.is_empty() {
Ok((MadeProgress, loc_tag, state))
} else {
let region = Region::across_all(
std::iter::once(&loc_ident.region)
.chain(loc_args.iter().map(|loc_arg| &loc_arg.region)),
);
let value =
Pattern::Apply(&*arena.alloc(loc_tag), loc_args.into_bump_slice());
Ok((MadeProgress, Located { region, value }, state))
}
} else {
Ok((MadeProgress, loc_tag, state))
}
}
Ident::Access { module_name, parts } => {
// Plain identifiers (e.g. `foo`) are allowed in patterns, but
// more complex ones (e.g. `Foo.bar` or `foo.bar.baz`) are not.
if crate::keyword::KEYWORDS.contains(&parts[0]) {
Err((
NoProgress,
EPattern::End(original_state.line, original_state.column),
original_state,
))
} else if module_name.is_empty() && parts.len() == 1 {
Ok((
MadeProgress,
Located {
region: loc_ident.region,
value: Pattern::Identifier(parts[0]),
},
state,
))
} else {
let malformed_str = if module_name.is_empty() {
parts.join(".")
} else {
format!("{}.{}", module_name, parts.join("."))
};
Ok((
MadeProgress,
Located {
region: loc_ident.region,
value: Pattern::Malformed(
String::from_str_in(&malformed_str, &arena).into_bump_str(),
),
},
state,
))
}
}
Ident::AccessorFunction(string) => Ok((
MadeProgress,
Located {
region: loc_ident.region,
value: Pattern::Malformed(string),
},
state,
)),
Ident::Malformed(malformed) => {
debug_assert!(!malformed.is_empty());
Err((
MadeProgress,
EPattern::Start(state.line, state.column),
state,
))
}
}
}
}
pub fn underscore_pattern<'a>() -> impl Parser<'a, Pattern<'a>, SyntaxError<'a>> {
specialize(|e, _, _| SyntaxError::Pattern(e), underscore_pattern_help())
}
fn underscore_pattern_help<'a>() -> impl Parser<'a, Pattern<'a>, EPattern<'a>> {
move |arena: &'a Bump, state: State<'a>| {
let (_, _, next_state) = word1(b'_', EPattern::Underscore).parse(arena, state)?;
let (_, output, final_state) =
optional(lowercase_ident_pattern).parse(arena, next_state)?;
match output {
Some(name) => Ok((MadeProgress, Pattern::Underscore(name), final_state)),
None => Ok((MadeProgress, Pattern::Underscore(&""), final_state)),
}
}
}
fn lowercase_ident_pattern<'a>(
arena: &'a Bump,
state: State<'a>,
) -> ParseResult<'a, &'a str, EPattern<'a>> {
let row = state.line;
let col = state.column;
specialize(move |_, _, _| EPattern::End(row, col), lowercase_ident()).parse(arena, state)
}
pub fn record_pattern<'a>(min_indent: u16) -> impl Parser<'a, Pattern<'a>, SyntaxError<'a>> {
specialize(
|e, r, c| SyntaxError::Pattern(EPattern::Record(e, r, c)),
record_pattern_help(min_indent),
)
}
#[inline(always)]
fn record_pattern_help<'a>(min_indent: u16) -> impl Parser<'a, Pattern<'a>, PRecord<'a>> {
move |arena, state| {
let (_, (fields, final_comments), state) = collection_trailing_sep_e!(
// word1_check_indent!(b'{', PRecord::Open, min_indent, PRecord::IndentOpen),
word1(b'{', PRecord::Open),
record_pattern_field(min_indent),
word1(b',', PRecord::End),
// word1_check_indent!(b'}', PRecord::End, min_indent, PRecord::IndentEnd),
word1(b'}', PRecord::End),
min_indent,
PRecord::Open,
PRecord::Space,
PRecord::IndentEnd
)
.parse(arena, state)?;
// TODO
let _unused = final_comments;
let result = Pattern::RecordDestructure(fields.into_bump_slice());
Ok((MadeProgress, result, state))
}
}
fn record_pattern_field<'a>(min_indent: u16) -> impl Parser<'a, Located<Pattern<'a>>, PRecord<'a>> {
use crate::parser::Either::*;
move |arena, state: State<'a>| {
// You must have a field name, e.g. "email"
// using the initial row/col is important for error reporting
let row = state.line;
let col = state.column;
let (progress, loc_label, state) = loc!(specialize(
move |_, _, _| PRecord::Field(row, col),
lowercase_ident()
))
.parse(arena, state)?;
debug_assert_eq!(progress, MadeProgress);
let (_, spaces, state) =
space0_e(min_indent, PRecord::Space, PRecord::IndentEnd).parse(arena, state)?;
// Having a value is optional; both `{ email }` and `{ email: blah }` work.
// (This is true in both literals and types.)
let (_, opt_loc_val, state) = optional(either!(
word1(b':', PRecord::Colon),
word1(b'?', PRecord::Optional)
))
.parse(arena, state)?;
match opt_loc_val {
Some(First(_)) => {
let val_parser = specialize_ref(PRecord::Syntax, loc_pattern(min_indent));
let (_, loc_val, state) =
space0_before_e(val_parser, min_indent, PRecord::Space, PRecord::IndentColon)
.parse(arena, state)?;
let Located {
value: label,
region,
} = loc_label;
let region = Region::span_across(&region, &loc_val.region);
Ok((
MadeProgress,
Located::at(
region,
Pattern::RequiredField(
label,
// TODO spaces are dropped here
// arena.alloc(arena.alloc(value).with_spaces_before(spaces, region)),
arena.alloc(loc_val),
),
),
state,
))
}
Some(Second(_)) => {
let val_parser =
specialize_ref(PRecord::Syntax, loc!(crate::expr::expr(min_indent)));
let (_, loc_val, state) =
space0_before_e(val_parser, min_indent, PRecord::Space, PRecord::IndentColon)
.parse(arena, state)?;
let Located {
value: label,
region,
} = loc_label;
let region = Region::span_across(&region, &loc_val.region);
Ok((
MadeProgress,
Located::at(
region,
Pattern::OptionalField(
label,
// TODO spaces are dropped
// arena.alloc(arena.alloc(value).with_spaces_before(spaces, region)),
arena.alloc(loc_val),
),
),
state,
))
}
// If no value was provided, record it as a Var.
// Canonicalize will know what to do with a Var later.
None => {
let Located { value, region } = loc_label;
let value = if !spaces.is_empty() {
Pattern::SpaceAfter(arena.alloc(Pattern::Identifier(value)), spaces)
} else {
Pattern::Identifier(value)
};
Ok((MadeProgress, Located::at(region, value), state))
}
}
}
}

View file

@ -66,7 +66,7 @@ fn term<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>, Typ
and!(
skip_second!(
backtrackable(space0_e(min_indent, Type::TSpace, Type::TIndentEnd)),
crate::parser::keyword_e(keyword::AS, Type::TEnd(0, 0))
crate::parser::keyword_e(keyword::AS, Type::TEnd)
),
space0_before_e(
term(min_indent),
@ -144,7 +144,6 @@ fn loc_applied_arg<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotatio
fn loc_type_in_parens<'a>(
min_indent: u16,
) -> impl Parser<'a, Located<TypeAnnotation<'a>>, TInParens<'a>> {
// TODO what if the middle parser returns EOF?
between!(
word1(b'(', TInParens::Open),
space0_around_e(

View file

@ -24,6 +24,10 @@ fn note_for_record_type_indent<'a>(alloc: &'a RocDocAllocator<'a>) -> RocDocBuil
alloc.note("I may be confused by indentation")
}
fn note_for_record_pattern_indent<'a>(alloc: &'a RocDocAllocator<'a>) -> RocDocBuilder<'a> {
alloc.note("I may be confused by indentation")
}
fn note_for_tag_union_type_indent<'a>(alloc: &'a RocDocAllocator<'a>) -> RocDocBuilder<'a> {
alloc.note("I may be confused by indentation")
}
@ -50,6 +54,14 @@ fn hint_for_private_tag_name<'a>(alloc: &'a RocDocAllocator<'a>) -> RocDocBuilde
])
}
fn record_patterns_look_like<'a>(alloc: &'a RocDocAllocator<'a>) -> RocDocBuilder<'a> {
alloc.concat(vec![
alloc.reflow(r"Record pattern look like "),
alloc.parser_suggestion("{ name, age: currentAge },"),
alloc.reflow(" so I was expecting to see a field name next."),
])
}
fn to_syntax_report<'a>(
alloc: &'a RocDocAllocator<'a>,
filename: PathBuf,
@ -132,10 +144,581 @@ fn to_syntax_report<'a>(
}
}
Type(typ) => to_type_report(alloc, filename, &typ, 0, 0),
Pattern(pat) => to_pattern_report(alloc, filename, &pat, 0, 0),
Expr(expr) => to_expr_report(alloc, filename, Context::InDef, &expr, 0, 0),
_ => todo!("unhandled parse error: {:?}", parse_problem),
}
}
enum Context {
InNode(Node, Row, Col, Box<Context>),
InDef,
}
enum Node {
WhenCondition,
WhenBranch,
// WhenIfGuard,
}
fn to_expr_report<'a>(
alloc: &'a RocDocAllocator<'a>,
filename: PathBuf,
context: Context,
parse_problem: &roc_parse::parser::EExpr<'a>,
_start_row: Row,
_start_col: Col,
) -> Report<'a> {
use roc_parse::parser::EExpr;
match parse_problem {
EExpr::When(when, row, col) => to_when_report(alloc, filename, context, &when, *row, *col),
_ => todo!("unhandled parse error: {:?}", parse_problem),
}
}
fn to_when_report<'a>(
alloc: &'a RocDocAllocator<'a>,
filename: PathBuf,
context: Context,
parse_problem: &roc_parse::parser::When<'a>,
start_row: Row,
start_col: Col,
) -> Report<'a> {
use roc_parse::parser::When;
match *parse_problem {
When::IfGuard(nested, row, col) => match what_is_next(alloc.src_lines, row, col) {
Next::Token("->") => {
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
let region = Region::from_row_col(row, col);
let doc = alloc.stack(vec![
alloc.reflow(
r"I just started parsing an if guard, but there is no guard condition:",
),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("Try adding an expression before the arrow!")
]),
]);
Report {
filename,
doc,
title: "IF GUARD NO CONDITION".to_string(),
}
}
_ => {
// to_expr_report(
// alloc,
// filename,
// Context::InNode(Node::WhenIfGuard, start_row, start_col, Box::new(context)),
// expr,
// row,
// col,
// )
to_syntax_report(alloc, filename, nested, row, col)
}
},
When::Arrow(row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
let region = Region::from_row_col(row, col);
let doc = alloc.stack(vec![
alloc.concat(vec![
alloc.reflow(r"I am partway through parsing a "),
alloc.keyword("when"),
alloc.reflow(r" expression, but got stuck here:"),
]),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![alloc.reflow("I was expecting to see an arrow next.")]),
note_for_when_indent_error(alloc),
]);
Report {
filename,
doc,
title: "MISSING ARROW".to_string(),
}
}
When::Syntax(syntax, row, col) => to_syntax_report(alloc, filename, syntax, row, col),
When::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col),
When::Branch(expr, row, col) => to_expr_report(
alloc,
filename,
Context::InNode(Node::WhenBranch, start_row, start_col, Box::new(context)),
expr,
row,
col,
),
When::Condition(expr, row, col) => to_expr_report(
alloc,
filename,
Context::InNode(Node::WhenCondition, start_row, start_col, Box::new(context)),
expr,
row,
col,
),
When::Bar(row, col) => to_unfinished_when_report(
alloc,
filename,
row,
col,
start_row,
start_col,
alloc.concat(vec![
alloc.reflow(r"I just saw a "),
alloc.parser_suggestion(r"|"),
alloc.reflow(r" so I was expecting to see a pattern next."),
]),
),
When::IfToken(_row, _col) => unreachable!("the if-token is optional"),
When::When(_row, _col) => unreachable!("another branch would be taken"),
When::Is(row, col) | When::IndentIs(row, col) => to_unfinished_when_report(
alloc,
filename,
row,
col,
start_row,
start_col,
alloc.concat(vec![
alloc.reflow(r"I was expecting to see the "),
alloc.keyword("is"),
alloc.reflow(r" keyword next."),
]),
),
When::IndentCondition(row, col) => to_unfinished_when_report(
alloc,
filename,
row,
col,
start_row,
start_col,
alloc.concat(vec![
alloc.reflow(r"I was expecting to see a expression next")
]),
),
When::IndentPattern(row, col) => to_unfinished_when_report(
alloc,
filename,
row,
col,
start_row,
start_col,
alloc.concat(vec![alloc.reflow(r"I was expecting to see a pattern next")]),
),
When::IndentArrow(row, col) => to_unfinished_when_report(
alloc,
filename,
row,
col,
start_row,
start_col,
alloc.concat(vec![
alloc.reflow(r"I just saw a pattern, so I was expecting to see a "),
alloc.parser_suggestion("->"),
alloc.reflow(" next."),
]),
),
When::IndentIfGuard(row, col) => to_unfinished_when_report(
alloc,
filename,
row,
col,
start_row,
start_col,
alloc.concat(vec![
alloc.reflow(r"I just saw the "),
alloc.keyword("if"),
alloc.reflow(" keyword, so I was expecting to see an expression next."),
]),
),
When::IndentBranch(row, col) => to_unfinished_when_report(
alloc,
filename,
row,
col,
start_row,
start_col,
alloc.concat(vec![
alloc.reflow(r"I was expecting to see an expression next. "),
alloc.reflow("What should I do when I run into this particular pattern?"),
]),
),
When::PatternAlignment(indent, row, col) => to_unfinished_when_report(
alloc,
filename,
row,
col,
start_row,
start_col,
alloc.concat(vec![
alloc.reflow(r"I suspect this is a pattern that is not indented enough? (by "),
alloc.text(indent.to_string()),
alloc.reflow(" spaces)"),
]),
),
When::Pattern(ref pat, row, col) => to_pattern_report(alloc, filename, pat, row, col),
}
}
fn to_unfinished_when_report<'a>(
alloc: &'a RocDocAllocator<'a>,
filename: PathBuf,
row: Row,
col: Col,
start_row: Row,
start_col: Col,
message: RocDocBuilder<'a>,
) -> Report<'a> {
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
let region = Region::from_row_col(row, col);
let doc = alloc.stack(vec![
alloc.concat(vec![
alloc.reflow(r"I was partway through parsing a "),
alloc.keyword("when"),
alloc.reflow(r" expression, but I got stuck here:"),
]),
alloc.region_with_subregion(surroundings, region),
message,
note_for_when_error(alloc),
]);
Report {
filename,
doc,
title: "UNFINISHED WHEN".to_string(),
}
}
fn note_for_when_error<'a>(alloc: &'a RocDocAllocator<'a>) -> RocDocBuilder<'a> {
alloc.stack(vec![
alloc.concat(vec![
alloc.note("Here is an example of a valid "),
alloc.keyword("when"),
alloc.reflow(r" expression for reference."),
]),
alloc.vcat(vec![
alloc.text("when List.first plants is").indent(4),
alloc.text("Ok n ->").indent(6),
alloc.text("n").indent(8),
alloc.text(""),
alloc.text("Err _ ->").indent(6),
alloc.text("200").indent(8),
]),
alloc.concat(vec![
alloc.reflow(
"Notice the indentation. All patterns are aligned, and each branch is indented",
),
alloc.reflow(" a bit more than the corresponding pattern. That is important!"),
]),
])
}
fn note_for_when_indent_error<'a>(alloc: &'a RocDocAllocator<'a>) -> RocDocBuilder<'a> {
alloc.stack(vec![
alloc.concat(vec![
alloc.note("Sometimes I get confused by indentation, so try to make your "),
alloc.keyword("when"),
alloc.reflow(r" look something like this:"),
]),
alloc.vcat(vec![
alloc.text("when List.first plants is").indent(4),
alloc.text("Ok n ->").indent(6),
alloc.text("n").indent(8),
alloc.text(""),
alloc.text("Err _ ->").indent(6),
alloc.text("200").indent(8),
]),
alloc.concat(vec![
alloc.reflow(
"Notice the indentation. All patterns are aligned, and each branch is indented",
),
alloc.reflow(" a bit more than the corresponding pattern. That is important!"),
]),
])
}
fn to_pattern_report<'a>(
alloc: &'a RocDocAllocator<'a>,
filename: PathBuf,
parse_problem: &roc_parse::parser::EPattern<'a>,
start_row: Row,
start_col: Col,
) -> Report<'a> {
use roc_parse::parser::EPattern;
match parse_problem {
EPattern::Start(row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, *row, *col);
let region = Region::from_row_col(*row, *col);
let doc = alloc.stack(vec![
alloc.reflow(r"I just started parsing a pattern, but I got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.note("I may be confused by indentation"),
]);
Report {
filename,
doc,
title: "UNFINISHED PATTERN".to_string(),
}
}
EPattern::Record(record, row, col) => {
to_precord_report(alloc, filename, &record, *row, *col)
}
_ => todo!("unhandled parse error: {:?}", parse_problem),
}
}
fn to_precord_report<'a>(
alloc: &'a RocDocAllocator<'a>,
filename: PathBuf,
parse_problem: &roc_parse::parser::PRecord<'a>,
start_row: Row,
start_col: Col,
) -> Report<'a> {
use roc_parse::parser::PRecord;
match *parse_problem {
PRecord::Open(row, col) => match what_is_next(alloc.src_lines, row, col) {
Next::Keyword(keyword) => {
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
let region = to_keyword_region(row, col, keyword);
let doc = alloc.stack(vec![
alloc.reflow(r"I just started parsing a record pattern, but I got stuck on this field name:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow(r"Looks like you are trying to use "),
alloc.keyword(keyword),
alloc.reflow(" as a field name, but that is a reserved word. Try using a different name!"),
]),
]);
Report {
filename,
doc,
title: "UNFINISHED RECORD PATTERN".to_string(),
}
}
_ => {
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
let region = Region::from_row_col(row, col);
let doc = alloc.stack(vec![
alloc.reflow(r"I just started parsing a record pattern, but I got stuck here:"),
alloc.region_with_subregion(surroundings, region),
record_patterns_look_like(alloc),
]);
Report {
filename,
doc,
title: "UNFINISHED RECORD PATTERN".to_string(),
}
}
},
PRecord::End(row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
let region = Region::from_row_col(row, col);
match what_is_next(alloc.src_lines, row, col) {
Next::Other(Some(c)) if c.is_alphabetic() => {
let doc = alloc.stack(vec![
alloc.reflow(r"I am partway through parsing a record pattern, but I got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow(
r"I was expecting to see a colon, question mark, comma or closing curly brace.",
),
]),
]);
Report {
filename,
doc,
title: "UNFINISHED RECORD PATTERN".to_string(),
}
}
_ => {
let doc = alloc.stack(vec![
alloc.reflow("I am partway through parsing a record pattern, but I got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow(
r"I was expecting to see a closing curly brace before this, so try adding a ",
),
alloc.parser_suggestion("}"),
alloc.reflow(" and see if that helps?"),
]),
]);
Report {
filename,
doc,
title: "UNFINISHED RECORD PATTERN".to_string(),
}
}
}
}
PRecord::Field(row, col) => match what_is_next(alloc.src_lines, row, col) {
Next::Keyword(keyword) => {
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
let region = to_keyword_region(row, col, keyword);
let doc = alloc.stack(vec![
alloc.reflow(r"I just started parsing a record pattern, but I got stuck on this field name:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow(r"Looks like you are trying to use "),
alloc.keyword(keyword),
alloc.reflow(" as a field name, but that is a reserved word. Try using a different name!"),
]),
]);
Report {
filename,
doc,
title: "UNFINISHED RECORD PATTERN".to_string(),
}
}
Next::Other(Some(',')) => todo!(),
Next::Other(Some('}')) => unreachable!("or is it?"),
_ => {
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
let region = Region::from_row_col(row, col);
let doc = alloc.stack(vec![
alloc.reflow(r"I am partway through parsing a record pattern, but I got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow(r"I was expecting to see another record field defined next, so I am looking for a name like "),
alloc.parser_suggestion("userName"),
alloc.reflow(" or "),
alloc.parser_suggestion("plantHight"),
alloc.reflow("."),
]),
]);
Report {
filename,
doc,
title: "PROBLEM IN RECORD PATTERN".to_string(),
}
}
},
PRecord::Colon(_, _) => {
unreachable!("because `{ foo }` is a valid field; the colon is not required")
}
PRecord::Optional(_, _) => {
unreachable!("because `{ foo }` is a valid field; the question mark is not required")
}
PRecord::Pattern(pattern, row, col) => {
to_pattern_report(alloc, filename, pattern, row, col)
}
PRecord::Syntax(syntax, row, col) => to_syntax_report(alloc, filename, syntax, row, col),
PRecord::IndentOpen(row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
let region = Region::from_row_col(row, col);
let doc = alloc.stack(vec![
alloc.reflow(r"I just started parsing a record pattern, but I got stuck here:"),
alloc.region_with_subregion(surroundings, region),
record_patterns_look_like(alloc),
note_for_record_pattern_indent(alloc),
]);
Report {
filename,
doc,
title: "UNFINISHED RECORD PATTERN".to_string(),
}
}
PRecord::IndentEnd(row, col) => {
match next_line_starts_with_close_curly(alloc.src_lines, row.saturating_sub(1)) {
Some((curly_row, curly_col)) => {
let surroundings =
Region::from_rows_cols(start_row, start_col, curly_row, curly_col);
let region = Region::from_row_col(curly_row, curly_col);
let doc = alloc.stack(vec![
alloc.reflow(
"I am partway through parsing a record pattern, but I got stuck here:",
),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("I need this curly brace to be indented more. Try adding more spaces before it!"),
]),
]);
Report {
filename,
doc,
title: "NEED MORE INDENTATION".to_string(),
}
}
None => {
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
let region = Region::from_row_col(row, col);
let doc = alloc.stack(vec![
alloc.reflow(
r"I am partway through parsing a record pattern, but I got stuck here:",
),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("I was expecting to see a closing curly "),
alloc.reflow("brace before this, so try adding a "),
alloc.parser_suggestion("}"),
alloc.reflow(" and see if that helps?"),
]),
note_for_record_pattern_indent(alloc),
]);
Report {
filename,
doc,
title: "UNFINISHED RECORD PATTERN".to_string(),
}
}
}
}
PRecord::IndentColon(_, _) => {
unreachable!("because `{ foo }` is a valid field; the colon is not required")
}
PRecord::IndentOptional(_, _) => {
unreachable!("because `{ foo }` is a valid field; the question mark is not required")
}
PRecord::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col),
}
}
fn to_type_report<'a>(
alloc: &'a RocDocAllocator<'a>,
filename: PathBuf,
@ -1027,6 +1610,7 @@ enum Next<'a> {
Keyword(&'a str),
// Operator(&'a str),
Close(&'a str, char),
Token(&'a str),
Other(Option<char>),
}
@ -1037,18 +1621,20 @@ fn what_is_next<'a>(source_lines: &'a [&'a str], row: Row, col: Col) -> Next<'a>
None => Next::Other(None),
Some(line) => {
let chars = &line[col_index..];
let mut it = chars.chars();
match roc_parse::keyword::KEYWORDS
.iter()
.find(|keyword| starts_with_keyword(chars, keyword))
{
Some(keyword) => Next::Keyword(keyword),
None => match chars.chars().next() {
None => match it.next() {
None => Next::Other(None),
Some(c) => match c {
')' => Next::Close("parenthesis", ')'),
']' => Next::Close("square bracket", ']'),
'}' => Next::Close("curly brace", '}'),
'-' if it.next() == Some('>') => Next::Token("->"),
// _ if is_symbol(c) => todo!("it's an operator"),
_ => Next::Other(Some(c)),
},

View file

@ -4774,4 +4774,179 @@ mod test_reporting {
),
)
}
#[test]
fn if_guard_without_condition() {
// this should get better with time
report_problem_as(
indoc!(
r#"
when Just 4 is
Just if ->
4
_ ->
2
"#
),
indoc!(
r#"
IF GUARD NO CONDITION
I just started parsing an if guard, but there is no guard condition:
1 when Just 4 is
2 Just if ->
^
Try adding an expression before the arrow!
"#
),
)
}
#[test]
fn empty_or_pattern() {
// this should get better with time
report_problem_as(
indoc!(
r#"
when Just 4 is
Just 4 | ->
4
_ ->
2
"#
),
indoc!(
r#"
PARSE PROBLEM
Unexpected token :
2 Just 4 | ->
^
"#
),
// indoc!(
// r#"
// ── UNFINISHED PATTERN ──────────────────────────────────────────────────────────
//
// I just started parsing a pattern, but I got stuck here:
//
// 2│ Just 4 | ->
// ^
//
// Note: I may be confused by indentation
// "#
// ),
)
}
#[test]
#[ignore]
fn pattern_binds_keyword() {
// this should get better with time
report_problem_as(
indoc!(
r#"
when Just 4 is
Just when ->
4
_ ->
2
"#
),
indoc!(
r#"
PARSE PROBLEM
Unexpected token :
2 Just if ->
^
"#
),
)
}
#[test]
fn when_missing_arrow() {
// this should get better with time
report_problem_as(
indoc!(
r#"
when 5 is
1 -> 2
_
"#
),
indoc!(
r#"
MISSING ARROW
I am partway through parsing a `when` expression, but got stuck here:
2 1 -> 2
3 _
^
I was expecting to see an arrow next.
Note: Sometimes I get confused by indentation, so try to make your `when`
look something like this:
when List.first plants is
Ok n ->
n
Err _ ->
200
Notice the indentation. All patterns are aligned, and each branch is
indented a bit more than the corresponding pattern. That is important!
"#
),
)
}
#[test]
fn when_outdented_branch() {
// this should get better with time
report_problem_as(
indoc!(
r#"
when 4 is
5 -> 2
_ -> 2
"#
),
indoc!(
r#"
UNFINISHED WHEN
I was partway through parsing a `when` expression, but I got stuck here:
3 _ -> 2
^
I suspect this is a pattern that is not indented enough? (by 2 spaces)
Note: Here is an example of a valid `when` expression for reference.
when List.first plants is
Ok n ->
n
Err _ ->
200
Notice the indentation. All patterns are aligned, and each branch is
indented a bit more than the corresponding pattern. That is important!
"#
),
)
}
}

View file

@ -211,6 +211,30 @@ mod solve_expr {
);
}
#[test]
fn string_from_int() {
infer_eq_without_problem(
indoc!(
r#"
Str.fromInt
"#
),
"Int * -> Str",
);
}
#[test]
fn string_from_utf8() {
infer_eq_without_problem(
indoc!(
r#"
Str.fromUtf8
"#
),
"List U8 -> Result Str [ BadUtf8 Utf8ByteProblem Nat ]*",
);
}
// #[test]
// fn block_string_literal() {
// infer_eq(

View file

@ -1,5 +1,6 @@
use crate::solved_types::{BuiltinAlias, SolvedType};
use crate::subs::VarId;
use crate::types::RecordField;
use roc_collections::all::{default_hasher, MutMap};
use roc_module::ident::TagName;
use roc_module::symbol::Symbol;
@ -298,7 +299,7 @@ pub fn aliases() -> MutMap<Symbol, BuiltinAlias> {
},
);
// Result a e : [ Ok a, Err e ]
// Result ok err : [ Ok ok, Err err ]
add_alias(
Symbol::RESULT_RESULT,
BuiltinAlias {
@ -311,6 +312,26 @@ pub fn aliases() -> MutMap<Symbol, BuiltinAlias> {
},
);
// Utf8ByteProblem : [ InvalidStartByte, UnexpectedEndOfSequence, ExpectedContinuation, OverlongEncoding, CodepointTooLarge, EncodesSurrogateHalf ]
add_alias(
Symbol::STR_UT8_BYTE_PROBLEM,
BuiltinAlias {
region: Region::zero(),
vars: Vec::new(),
typ: str_utf8_byte_problem_alias_content(),
},
);
// Utf8Problem : { byteIndex : Nat, problem : Utf8ByteProblem }
add_alias(
Symbol::STR_UT8_PROBLEM,
BuiltinAlias {
region: Region::zero(),
vars: Vec::new(),
typ: str_utf8_byte_problem_alias_content(),
},
);
aliases
}
@ -421,6 +442,15 @@ fn integer_alias_content(range: SolvedType) -> SolvedType {
single_private_tag(Symbol::NUM_AT_INTEGER, vec![range])
}
#[inline(always)]
pub fn u8_type() -> SolvedType {
SolvedType::Alias(
Symbol::NUM_U8,
vec![],
Box::new(int_alias_content(unsigned8_type())),
)
}
#[inline(always)]
pub fn binary64_type() -> SolvedType {
SolvedType::Alias(
@ -665,6 +695,57 @@ pub fn str_type() -> SolvedType {
SolvedType::Apply(Symbol::STR_STR, Vec::new())
}
#[inline(always)]
pub fn str_utf8_problem_type() -> SolvedType {
SolvedType::Alias(
Symbol::STR_UT8_PROBLEM,
Vec::new(),
Box::new(str_utf8_problem_alias_content()),
)
}
#[inline(always)]
pub fn str_utf8_problem_alias_content() -> SolvedType {
SolvedType::Record {
fields: vec![
("byteIndex".into(), RecordField::Required(nat_type())),
(
"problem".into(),
RecordField::Required(str_utf8_byte_problem_type()),
),
],
ext: Box::new(SolvedType::EmptyRecord),
}
}
#[inline(always)]
pub fn str_utf8_byte_problem_type() -> SolvedType {
SolvedType::Alias(
Symbol::STR_UT8_BYTE_PROBLEM,
Vec::new(),
Box::new(str_utf8_byte_problem_alias_content()),
)
}
#[inline(always)]
pub fn str_utf8_byte_problem_alias_content() -> SolvedType {
// 1. This must have the same values as the Zig struct Utf8ByteProblem in src/str.zig
// 2. This must be in alphabetical order
//
// [ CodepointTooLarge, EncodesSurrogateHalf, OverlongEncoding, InvalidStartByte, UnexpectedEndOfSequence, ExpectedContinuation ]
SolvedType::TagUnion(
vec![
(TagName::Global("CodepointTooLarge".into()), vec![]),
(TagName::Global("EncodesSurrogateHalf".into()), vec![]),
(TagName::Global("ExpectedContinuation".into()), vec![]),
(TagName::Global("InvalidStartByte".into()), vec![]),
(TagName::Global("OverlongEncoding".into()), vec![]),
(TagName::Global("UnexpectedEndOfSequence".into()), vec![]),
],
Box::new(SolvedType::EmptyTagUnion),
)
}
#[inline(always)]
pub fn set_type(a: SolvedType) -> SolvedType {
SolvedType::Apply(Symbol::SET_SET, vec![a])

View file

@ -69,6 +69,7 @@ pest = "2.1"
pest_derive = "2.1"
ropey = "1.2.0"
copypasta = "0.7.1"
indoc = "0.3.3"
[dependencies.bytemuck]
version = "1.4"
@ -77,7 +78,6 @@ features = ["derive"]
[dev-dependencies]
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"
criterion = "0.3"

View file

@ -0,0 +1,8 @@
use crate::graphics::colors as gr_colors;
use crate::graphics::colors::ColorTup;
use crate::ui::colors as ui_colors;
pub const BG_COL: ColorTup = (0.17, 0.17, 0.19, 1.0);
pub const EQUALS_SYNTAX_COL: ColorTup = (0.043, 0.0196, 0.102, 1.0);
pub const STRING_SYNTAX_COL: ColorTup = ui_colors::LIGHT_BRAND_COL;
pub const CODE_COL: ColorTup = gr_colors::WHITE;

View file

@ -1,5 +1,5 @@
use colored::*;
use snafu::{Backtrace, ErrorCompat, Snafu};
use snafu::{Backtrace, ErrorCompat, NoneError, ResultExt, Snafu};
//import errors as follows:
// `use crate::error::OutOfBounds;`
@ -21,22 +21,6 @@ pub enum EdError {
))]
ClipboardInitFailed { err_msg: String },
#[snafu(display(
"FileOpenFailed: failed to open file with path {} with the following error: {}.",
path_str,
err_msg
))]
FileOpenFailed { path_str: String, err_msg: String },
#[snafu(display("InvalidSelection: {}.", err_msg))]
InvalidSelection {
err_msg: String,
backtrace: Backtrace,
},
#[snafu(display("MissingGlyphDims: glyph_dim_rect_opt was None for model. It needs to be set using the example_code_glyph_rect function."))]
MissingGlyphDims { backtrace: Backtrace },
#[snafu(display(
"OutOfBounds: index {} was out of bounds for {} with length {}.",
index,
@ -50,8 +34,8 @@ pub enum EdError {
backtrace: Backtrace,
},
#[snafu(display("TextBufReadFailed: the file {} could be opened but we encountered the following error while trying to read it: {}.", path_str, err_msg))]
TextBufReadFailed { path_str: String, err_msg: String },
#[snafu(display("UIError: {}", msg))]
UIErrorBacktrace { msg: String, backtrace: Backtrace },
}
pub type EdResult<T, E = EdError> = std::result::Result<T, E>;
@ -64,6 +48,14 @@ pub fn print_err(err: &EdError) {
}
}
pub fn print_ui_err(err: &UIError) {
eprintln!("{}", format!("{}", err).truecolor(255, 0, 0));
if let Some(backtrace) = ErrorCompat::backtrace(err) {
eprintln!("{}", color_backtrace(backtrace));
}
}
fn color_backtrace(backtrace: &snafu::Backtrace) -> String {
let backtrace_str = format!("{}", backtrace);
let backtrace_split = backtrace_str.split('\n');
@ -110,3 +102,15 @@ impl From<EdError> for String {
format!("{}", ed_error)
}
}
use crate::ui::ui_error::UIError;
impl From<UIError> for EdError {
fn from(ui_err: UIError) -> Self {
let msg = format!("{}", ui_err);
// hack to handle EdError derive
let dummy_res: Result<(), NoneError> = Err(NoneError {});
dummy_res.context(UIErrorBacktrace { msg }).unwrap_err()
}
}

View file

@ -1,6 +1,8 @@
use crate::error::EdResult;
use crate::mvc::app_model::AppModel;
use crate::mvc::app_update::{handle_copy, handle_cut, handle_paste, pass_keydown_to_focused};
use crate::editor::ed_error::EdResult;
use crate::editor::mvc::app_model::AppModel;
use crate::editor::mvc::app_update::{
handle_copy, handle_cut, handle_paste, pass_keydown_to_focused,
};
use winit::event::VirtualKeyCode::*;
use winit::event::{ElementState, ModifiersState, VirtualKeyCode};
@ -15,10 +17,9 @@ pub fn handle_keydown(
}
match virtual_keycode {
Left => pass_keydown_to_focused(&modifiers, virtual_keycode, app_model),
Up => pass_keydown_to_focused(&modifiers, virtual_keycode, app_model),
Right => pass_keydown_to_focused(&modifiers, virtual_keycode, app_model),
Down => pass_keydown_to_focused(&modifiers, virtual_keycode, app_model),
Left | Up | Right | Down => {
pass_keydown_to_focused(&modifiers, virtual_keycode, app_model)?
}
Copy => handle_copy(app_model)?,
Paste => handle_paste(app_model)?,
@ -39,7 +40,7 @@ pub fn handle_keydown(
}
}
A => pass_keydown_to_focused(&modifiers, virtual_keycode, app_model),
A | Home | End => pass_keydown_to_focused(&modifiers, virtual_keycode, app_model)?,
_ => (),
}

448
editor/src/editor/main.rs Normal file
View file

@ -0,0 +1,448 @@
use super::keyboard_input;
use crate::editor::colors::BG_COL;
use crate::editor::colors::CODE_COL;
use crate::editor::ed_error::{print_err, print_ui_err};
use crate::editor::mvc::{app_model::AppModel, app_update, ed_model, ed_model::EdModel, ed_view};
use crate::graphics::colors::to_wgpu_color;
use crate::graphics::primitives::text::{
build_glyph_brush, example_code_glyph_rect, queue_code_text_draw, queue_text_draw, Text,
};
use crate::graphics::{
lowlevel::buffer::create_rect_buffers, lowlevel::ortho::update_ortho_buffer,
lowlevel::pipelines, style::CODE_FONT_SIZE, style::CODE_TXT_XY,
};
use crate::ui::{colors::TXT_COL, text::lines::Lines, text::text_pos::TextPos, ui_error::UIResult};
//use crate::resources::strings::NOTHING_OPENED;
use super::util::slice_get;
use crate::lang::{pool::Pool, scope::Scope};
use bumpalo::Bump;
use cgmath::Vector2;
use pipelines::RectResources;
use roc_module::symbol::{IdentIds, ModuleIds};
use roc_region::all::Region;
use roc_types::subs::VarStore;
use std::{error::Error, io, path::Path};
use wgpu::{CommandEncoder, RenderPass, TextureView};
use wgpu_glyph::GlyphBrush;
use winit::{
dpi::PhysicalSize,
event,
event::{Event, ModifiersState},
event_loop::ControlFlow,
};
// Inspired by:
// https://github.com/sotrh/learn-wgpu by Benjamin Hansen, licensed under the MIT license
// https://github.com/cloudhead/rgx by Alexis Sellier, licensed under the MIT license
// See this link to learn wgpu: https://sotrh.github.io/learn-wgpu/
/// The editor is actually launched from the CLI if you pass it zero arguments,
/// or if you provide it 1 or more files or directories to open on launch.
pub fn launch(filepaths: &[&Path]) -> io::Result<()> {
//TODO support using multiple filepaths
let first_path_opt = if !filepaths.is_empty() {
match slice_get(0, filepaths) {
Ok(path_ref_ref) => Some(*path_ref_ref),
Err(e) => {
eprintln!("{}", e);
None
}
}
} else {
None
};
run_event_loop(first_path_opt).expect("Error running event loop");
Ok(())
}
fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
env_logger::init();
// Open window and create a surface
let event_loop = winit::event_loop::EventLoop::new();
let window = winit::window::WindowBuilder::new()
.with_inner_size(PhysicalSize::new(1200.0, 1000.0))
.build(&event_loop)
.unwrap();
let instance = wgpu::Instance::new(wgpu::BackendBit::all());
let surface = unsafe { instance.create_surface(&window) };
// Initialize GPU
let (gpu_device, cmd_queue) = futures::executor::block_on(async {
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface: Some(&surface),
})
.await
.expect("Request adapter");
adapter
.request_device(
&wgpu::DeviceDescriptor {
features: wgpu::Features::empty(),
limits: wgpu::Limits::default(),
shader_validation: false,
},
None,
)
.await
.expect("Request device")
});
// Create staging belt and a local pool
let mut staging_belt = wgpu::util::StagingBelt::new(1024);
let mut local_pool = futures::executor::LocalPool::new();
let local_spawner = local_pool.spawner();
// Prepare swap chain
let render_format = wgpu::TextureFormat::Bgra8Unorm;
let mut size = window.inner_size();
let swap_chain_descr = wgpu::SwapChainDescriptor {
usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT,
format: render_format,
width: size.width,
height: size.height,
//Immediate may cause tearing, change present_mode if this becomes a problem
present_mode: wgpu::PresentMode::Immediate,
};
let mut swap_chain = gpu_device.create_swap_chain(&surface, &swap_chain_descr);
let rect_resources = pipelines::make_rect_pipeline(&gpu_device, &swap_chain_descr);
let mut glyph_brush = build_glyph_brush(&gpu_device, render_format)?;
let is_animating = true;
let ed_model_opt = if let Some(file_path) = file_path_opt {
let ed_model_res = ed_model::init_model(file_path);
match ed_model_res {
Ok(mut ed_model) => {
ed_model.glyph_dim_rect_opt = Some(example_code_glyph_rect(&mut glyph_brush));
Some(ed_model)
}
Err(e) => {
print_ui_err(&e);
None
}
}
} else {
None
};
let mut app_model = AppModel::init(ed_model_opt);
let mut keyboard_modifiers = ModifiersState::empty();
// This arena is never cleared and should only be used for allocations that occur rarely
let arena = Bump::new();
let mut rects_arena = Bump::new();
// Render loop
window.request_redraw();
event_loop.run(move |event, _, control_flow| {
// TODO dynamically switch this on/off depending on whether any
// animations are running. Should conserve CPU usage and battery life!
if is_animating {
*control_flow = ControlFlow::Poll;
} else {
*control_flow = ControlFlow::Wait;
}
match event {
//Close
Event::WindowEvent {
event: event::WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
//Resize
Event::WindowEvent {
event: event::WindowEvent::Resized(new_size),
..
} => {
size = new_size;
swap_chain = gpu_device.create_swap_chain(
&surface,
&wgpu::SwapChainDescriptor {
usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT,
format: render_format,
width: size.width,
height: size.height,
//Immediate may cause tearing, change present_mode if this becomes a problem
present_mode: wgpu::PresentMode::Immediate,
},
);
update_ortho_buffer(
size.width,
size.height,
&gpu_device,
&rect_resources.ortho.buffer,
&cmd_queue,
);
}
//Received Character
Event::WindowEvent {
event: event::WindowEvent::ReceivedCharacter(ch),
..
} => {
if let Err(e) = app_update::handle_new_char(&ch, &mut app_model) {
print_err(&e)
}
}
//Keyboard Input
Event::WindowEvent {
event: event::WindowEvent::KeyboardInput { input, .. },
..
} => {
if let Some(virtual_keycode) = input.virtual_keycode {
if let Some(ref mut ed_model) = app_model.ed_model_opt {
if ed_model.has_focus {
let keydown_res = keyboard_input::handle_keydown(
input.state,
virtual_keycode,
keyboard_modifiers,
&mut app_model,
);
if let Err(e) = keydown_res {
print_err(&e)
}
}
}
}
}
//Modifiers Changed
Event::WindowEvent {
event: event::WindowEvent::ModifiersChanged(modifiers),
..
} => {
keyboard_modifiers = modifiers;
}
Event::MainEventsCleared => window.request_redraw(),
Event::RedrawRequested { .. } => {
// Get a command encoder for the current frame
let mut encoder =
gpu_device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Redraw"),
});
let frame = swap_chain
.get_current_frame()
.expect("Failed to acquire next SwapChainFrame")
.output;
if let Some(ed_model) = &app_model.ed_model_opt {
//TODO don't pass invisible lines
queue_editor_text(
&size,
&ed_model.text.all_lines(&arena),
ed_model.text.caret_w_select.caret_pos,
CODE_TXT_XY.into(),
&mut glyph_brush,
);
} else {
// queue_no_file_text(&size, NOTHING_OPENED, CODE_TXT_XY.into(), &mut glyph_brush);
let mut pool = Pool::with_capacity(12);
let mut var_store = VarStore::default();
let dep_idents = IdentIds::exposed_builtins(8);
let mut module_ids = ModuleIds::default();
let exposed_ident_ids = IdentIds::default();
let home = module_ids.get_or_insert(&"Home".into());
let mut env = crate::lang::expr::Env::new(
home,
&arena,
&mut pool,
&mut var_store,
dep_idents,
&module_ids,
exposed_ident_ids,
);
let mut scope = Scope::new(home, env.pool, env.var_store);
let region = Region::new(0, 0, 0, 0);
let (expr2, _) = crate::lang::expr::str_to_expr2(
&arena,
"{ x: 2, y: 5 }",
&mut env,
&mut scope,
region,
)
.unwrap();
super::render_ast::render_expr2(
&mut env,
&expr2,
&size,
CODE_TXT_XY.into(),
&mut glyph_brush,
);
}
rects_arena.reset();
match draw_all_rects(
&app_model.ed_model_opt,
&rects_arena,
&mut encoder,
&frame.view,
&gpu_device,
&rect_resources,
) {
Ok(()) => (),
Err(e) => {
print_ui_err(&e);
begin_render_pass(&mut encoder, &frame.view);
}
}
// draw all text
glyph_brush
.draw_queued(
&gpu_device,
&mut staging_belt,
&mut encoder,
&frame.view,
size.width,
size.height,
)
.expect("Draw queued");
staging_belt.finish();
cmd_queue.submit(Some(encoder.finish()));
// Recall unused staging buffers
use futures::task::SpawnExt;
local_spawner
.spawn(staging_belt.recall())
.expect("Recall staging belt");
local_pool.run_until_stalled();
}
_ => {
*control_flow = winit::event_loop::ControlFlow::Wait;
}
}
})
}
fn draw_all_rects(
ed_model_opt: &Option<EdModel>,
arena: &Bump,
encoder: &mut CommandEncoder,
texture_view: &TextureView,
gpu_device: &wgpu::Device,
rect_resources: &RectResources,
) -> UIResult<()> {
if let Some(ed_model) = ed_model_opt {
let all_rects = ed_view::create_ed_rects(ed_model, arena)?;
let rect_buffers = create_rect_buffers(gpu_device, encoder, &all_rects);
let mut render_pass = begin_render_pass(encoder, texture_view);
render_pass.set_pipeline(&rect_resources.pipeline);
render_pass.set_bind_group(0, &rect_resources.ortho.bind_group, &[]);
render_pass.set_vertex_buffer(0, rect_buffers.vertex_buffer.slice(..));
render_pass.set_index_buffer(rect_buffers.index_buffer.slice(..));
render_pass.draw_indexed(0..rect_buffers.num_rects, 0, 0..1);
} else {
// need to begin render pass to clear screen
begin_render_pass(encoder, texture_view);
}
Ok(())
}
fn begin_render_pass<'a>(
encoder: &'a mut CommandEncoder,
texture_view: &'a TextureView,
) -> RenderPass<'a> {
let bg_color = to_wgpu_color(BG_COL);
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
attachment: texture_view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(bg_color),
store: true,
},
}],
depth_stencil_attachment: None,
})
}
// returns bounding boxes for every glyph
fn queue_editor_text(
size: &PhysicalSize<u32>,
editor_lines: &str,
caret_pos: TextPos,
code_coords: Vector2<f32>,
glyph_brush: &mut GlyphBrush<()>,
) {
let area_bounds = (size.width as f32, size.height as f32).into();
let code_text = Text {
position: code_coords,
area_bounds,
color: CODE_COL.into(),
text: editor_lines,
size: CODE_FONT_SIZE,
..Default::default()
};
let s = format!("Ln {}, Col {}", caret_pos.line, caret_pos.column);
let text = s.as_str();
let caret_pos_label = Text {
position: ((size.width as f32) - 150.0, (size.height as f32) - 40.0).into(),
area_bounds,
color: TXT_COL.into(),
text,
size: 25.0,
..Default::default()
};
queue_text_draw(&caret_pos_label, glyph_brush);
queue_code_text_draw(&code_text, glyph_brush);
}
fn _queue_no_file_text(
size: &PhysicalSize<u32>,
text: &str,
text_coords: Vector2<f32>,
glyph_brush: &mut GlyphBrush<()>,
) {
let area_bounds = (size.width as f32, size.height as f32).into();
let code_text = Text {
position: text_coords,
area_bounds,
color: CODE_COL.into(),
text,
size: CODE_FONT_SIZE,
..Default::default()
};
queue_text_draw(&code_text, glyph_brush);
}

8
editor/src/editor/mod.rs Normal file
View file

@ -0,0 +1,8 @@
mod colors;
mod ed_error;
mod keyboard_input;
pub mod main;
mod mvc;
mod render_ast;
pub mod syntax_highlight; // TODO remove pub once we have ast based syntax highlighting
mod util;

View file

@ -1,6 +1,9 @@
use super::ed_model::EdModel;
use crate::error::EdError::{ClipboardInitFailed, ClipboardReadFailed, ClipboardWriteFailed};
use crate::error::{print_err, EdResult};
use crate::editor::ed_error::{
print_err,
EdError::{ClipboardInitFailed, ClipboardReadFailed, ClipboardWriteFailed},
EdResult,
};
use copypasta::{ClipboardContext, ClipboardProvider};
use std::fmt;

View file

@ -1,14 +1,17 @@
use super::app_model;
use super::app_model::AppModel;
use super::ed_model::Position;
use super::ed_update;
use crate::error::EdResult;
use crate::editor::ed_error::EdResult;
use crate::ui::text::{
lines::{MutSelectableLines, SelectableLines},
text_pos::TextPos,
};
use crate::ui::ui_error::UIResult;
use winit::event::{ModifiersState, VirtualKeyCode};
pub fn handle_copy(app_model: &mut AppModel) -> EdResult<()> {
if let Some(ref mut ed_model) = app_model.ed_model_opt {
if ed_model.has_focus {
let selected_str_opt = ed_model.get_selected_str()?;
let selected_str_opt = ed_model.text.get_selected_str()?;
if let Some(selected_str) = selected_str_opt {
app_model::set_clipboard_txt(&mut app_model.clipboard_opt, selected_str)?;
@ -30,43 +33,39 @@ pub fn handle_paste(app_model: &mut AppModel) -> EdResult<()> {
let last_line_nr_chars = rsplit_iter.next().unwrap().len();
let clipboard_nr_lines = rsplit_iter.count();
let old_caret_pos = ed_model.caret_pos;
let old_caret_pos = ed_model.text.caret_w_select.caret_pos;
let selection_opt = ed_model.text.get_selection();
if let Some(selection) = ed_model.selection_opt {
if let Some(selection) = selection_opt {
let start_caret_pos = selection.start_pos;
ed_model.text_buf.del_selection(selection)?;
ed_model.selection_opt = None;
ed_model.text.del_selection()?;
ed_model
.text_buf
.insert_str(start_caret_pos, &clipboard_content)?;
ed_model.text.insert_str(&clipboard_content)?;
if clipboard_nr_lines > 0 {
ed_model.caret_pos = Position {
ed_model.text.set_caret(TextPos {
line: start_caret_pos.line + clipboard_nr_lines,
column: last_line_nr_chars,
}
})
} else {
ed_model.caret_pos = Position {
ed_model.text.set_caret(TextPos {
line: start_caret_pos.line,
column: start_caret_pos.column + last_line_nr_chars,
}
})
}
} else {
ed_model
.text_buf
.insert_str(old_caret_pos, &clipboard_content)?;
ed_model.text.insert_str(&clipboard_content)?;
if clipboard_nr_lines > 0 {
ed_model.caret_pos = Position {
ed_model.text.set_caret(TextPos {
line: old_caret_pos.line + clipboard_nr_lines,
column: last_line_nr_chars,
}
})
} else {
ed_model.caret_pos = Position {
ed_model.text.set_caret(TextPos {
line: old_caret_pos.line,
column: old_caret_pos.column + last_line_nr_chars,
}
})
}
}
}
@ -79,12 +78,12 @@ pub fn handle_paste(app_model: &mut AppModel) -> EdResult<()> {
pub fn handle_cut(app_model: &mut AppModel) -> EdResult<()> {
if let Some(ref mut ed_model) = app_model.ed_model_opt {
if ed_model.has_focus {
let selected_str_opt = ed_model.get_selected_str()?;
let selected_str_opt = ed_model.text.get_selected_str()?;
if let Some(selected_str) = selected_str_opt {
app_model::set_clipboard_txt(&mut app_model.clipboard_opt, selected_str)?;
ed_model.del_selection()?;
ed_model.text.del_selection()?;
}
}
}
@ -96,18 +95,20 @@ pub fn pass_keydown_to_focused(
modifiers: &ModifiersState,
virtual_keycode: VirtualKeyCode,
app_model: &mut AppModel,
) {
) -> UIResult<()> {
if let Some(ref mut ed_model) = app_model.ed_model_opt {
if ed_model.has_focus {
ed_update::handle_key_down(modifiers, virtual_keycode, ed_model);
ed_model.text.handle_key_down(modifiers, virtual_keycode)?;
}
}
Ok(())
}
pub fn handle_new_char(received_char: &char, app_model: &mut AppModel) -> EdResult<()> {
if let Some(ref mut ed_model) = app_model.ed_model_opt {
if ed_model.has_focus {
ed_update::handle_new_char(received_char, ed_model)?;
ed_model.text.handle_new_char(received_char)?;
}
}
@ -116,25 +117,24 @@ pub fn handle_new_char(received_char: &char, app_model: &mut AppModel) -> EdResu
#[cfg(test)]
pub mod test_app_update {
use crate::mvc::app_model;
use crate::mvc::app_model::{AppModel, Clipboard};
use crate::mvc::app_update::{handle_copy, handle_cut, handle_paste};
use crate::mvc::ed_model::{EdModel, Position, RawSelection};
use crate::mvc::ed_update::test_ed_update::gen_caret_text_buf;
use crate::selection::test_selection::{all_lines_vec, convert_selection_to_dsl};
use crate::text_buffer::TextBuffer;
use crate::editor::mvc::app_model;
use crate::editor::mvc::app_model::{AppModel, Clipboard};
use crate::editor::mvc::app_update::{handle_copy, handle_cut, handle_paste};
use crate::editor::mvc::ed_model::EdModel;
use crate::ui::text::{
big_selectable_text::test_big_sel_text::{
all_lines_vec, convert_selection_to_dsl, gen_big_text,
},
big_selectable_text::BigSelectableText,
};
pub fn mock_app_model(
text_buf: TextBuffer,
caret_pos: Position,
selection_opt: Option<RawSelection>,
big_sel_text: BigSelectableText,
clipboard_opt: Option<Clipboard>,
) -> AppModel {
AppModel {
ed_model_opt: Some(EdModel {
text_buf,
caret_pos,
selection_opt,
text: big_sel_text,
glyph_dim_rect_opt: None,
has_focus: true,
}),
@ -147,9 +147,9 @@ pub mod test_app_update {
expected_clipboard_content: &str,
clipboard_opt: Option<Clipboard>,
) -> Result<Option<Clipboard>, String> {
let (caret_pos, selection_opt, pre_text_buf) = gen_caret_text_buf(pre_lines_str)?;
let pre_text_buf = gen_big_text(pre_lines_str)?;
let mut app_model = mock_app_model(pre_text_buf, caret_pos, selection_opt, clipboard_opt);
let mut app_model = mock_app_model(pre_text_buf, clipboard_opt);
handle_copy(&mut app_model)?;
@ -166,21 +166,18 @@ pub mod test_app_update {
expected_post_lines_str: &[&str],
clipboard_opt: Option<Clipboard>,
) -> Result<Option<Clipboard>, String> {
let (caret_pos, selection_opt, pre_text_buf) = gen_caret_text_buf(pre_lines_str)?;
let pre_big_text = gen_big_text(pre_lines_str)?;
let mut app_model = mock_app_model(pre_text_buf, caret_pos, selection_opt, clipboard_opt);
let mut app_model = mock_app_model(pre_big_text, clipboard_opt);
app_model::set_clipboard_txt(&mut app_model.clipboard_opt, clipboard_content)?;
handle_paste(&mut app_model)?;
let ed_model = app_model.ed_model_opt.unwrap();
let mut text_buf_lines = all_lines_vec(&ed_model.text_buf);
let post_lines_str = convert_selection_to_dsl(
ed_model.selection_opt,
ed_model.caret_pos,
&mut text_buf_lines,
)?;
let mut text_lines = all_lines_vec(&ed_model.text);
let post_lines_str =
convert_selection_to_dsl(ed_model.text.caret_w_select, &mut text_lines)?;
assert_eq!(post_lines_str, expected_post_lines_str);
@ -193,9 +190,9 @@ pub mod test_app_update {
expected_post_lines_str: &[&str],
clipboard_opt: Option<Clipboard>,
) -> Result<Option<Clipboard>, String> {
let (caret_pos, selection_opt, pre_text_buf) = gen_caret_text_buf(pre_lines_str)?;
let pre_big_text = gen_big_text(pre_lines_str)?;
let mut app_model = mock_app_model(pre_text_buf, caret_pos, selection_opt, clipboard_opt);
let mut app_model = mock_app_model(pre_big_text, clipboard_opt);
handle_cut(&mut app_model)?;
@ -204,12 +201,9 @@ pub mod test_app_update {
assert_eq!(clipboard_content, expected_clipboard_content);
let ed_model = app_model.ed_model_opt.unwrap();
let mut text_buf_lines = all_lines_vec(&ed_model.text_buf);
let post_lines_str = convert_selection_to_dsl(
ed_model.selection_opt,
ed_model.caret_pos,
&mut text_buf_lines,
)?;
let mut text_lines = all_lines_vec(&ed_model.text);
let post_lines_str =
convert_selection_to_dsl(ed_model.text.caret_w_select, &mut text_lines)?;
assert_eq!(post_lines_str, expected_post_lines_str);
@ -236,6 +230,7 @@ pub mod test_app_update {
)?;
// paste
clipboard_opt = assert_paste(&["|"], "", &["|"], clipboard_opt)?;
clipboard_opt = assert_paste(&["|"], "a", &["a|"], clipboard_opt)?;
clipboard_opt = assert_paste(&["a|"], "b", &["ab|"], clipboard_opt)?;

View file

@ -0,0 +1,19 @@
use crate::graphics::primitives::rect::Rect;
use crate::ui::text::big_selectable_text::{from_path, BigSelectableText};
use crate::ui::ui_error::UIResult;
use std::path::Path;
#[derive(Debug)]
pub struct EdModel {
pub text: BigSelectableText,
pub glyph_dim_rect_opt: Option<Rect>,
pub has_focus: bool,
}
pub fn init_model(file_path: &Path) -> UIResult<EdModel> {
Ok(EdModel {
text: from_path(file_path)?,
glyph_dim_rect_opt: None,
has_focus: true,
})
}

View file

@ -1,34 +1,38 @@
use super::ed_model::{EdModel, Position};
use crate::error::{EdResult, MissingGlyphDims};
use crate::graphics::colors::CARET_COLOR;
use super::ed_model::EdModel;
use crate::graphics::primitives::rect::Rect;
use crate::selection::create_selection_rects;
use crate::ui::colors::CARET_COL;
use crate::ui::text::{selection::create_selection_rects, text_pos::TextPos};
use crate::ui::ui_error::MissingGlyphDims;
use crate::ui::ui_error::UIResult;
use bumpalo::collections::Vec as BumpVec;
use bumpalo::Bump;
use snafu::ensure;
//TODO add editor text here as well
pub fn create_ed_rects<'a>(ed_model: &EdModel, arena: &'a Bump) -> EdResult<BumpVec<'a, Rect>> {
pub fn create_ed_rects<'a>(ed_model: &EdModel, arena: &'a Bump) -> UIResult<BumpVec<'a, Rect>> {
ensure!(ed_model.glyph_dim_rect_opt.is_some(), MissingGlyphDims {});
let glyph_rect = ed_model.glyph_dim_rect_opt.unwrap();
let mut all_rects: BumpVec<Rect> = BumpVec::new_in(arena);
if let Some(selection) = ed_model.selection_opt {
let selection_opt = ed_model.text.caret_w_select.selection_opt;
if let Some(selection) = selection_opt {
let mut selection_rects =
create_selection_rects(selection, &ed_model.text_buf, &glyph_rect, &arena)?;
create_selection_rects(selection, &ed_model.text, &glyph_rect, &arena)?;
all_rects.append(&mut selection_rects);
}
all_rects.push(make_caret_rect(ed_model.caret_pos, &glyph_rect));
let caret_pos = ed_model.text.caret_w_select.caret_pos;
all_rects.push(make_caret_rect(caret_pos, &glyph_rect));
Ok(all_rects)
}
fn make_caret_rect(caret_pos: Position, glyph_dim_rect: &Rect) -> Rect {
fn make_caret_rect(caret_pos: TextPos, glyph_dim_rect: &Rect) -> Rect {
let caret_y =
glyph_dim_rect.top_left_coords.y + (caret_pos.line as f32) * glyph_dim_rect.height;
@ -39,6 +43,6 @@ fn make_caret_rect(caret_pos: Position, glyph_dim_rect: &Rect) -> Rect {
top_left_coords: (caret_x, caret_y).into(),
height: glyph_dim_rect.height,
width: 2.0,
color: CARET_COLOR,
color: CARET_COL,
}
}

View file

@ -1,5 +1,4 @@
pub mod app_model;
pub mod app_update;
pub mod ed_model;
pub mod ed_update;
pub mod ed_view;

View file

@ -0,0 +1,258 @@
use crate::editor::colors::CODE_COL;
use crate::{
graphics::{
primitives::text::{queue_code_text_draw, Text},
style::CODE_FONT_SIZE,
},
lang::{ast::Expr2, expr::Env},
};
use cgmath::Vector2;
use wgpu_glyph::GlyphBrush;
use winit::dpi::PhysicalSize;
pub fn render_expr2<'a>(
env: &mut Env<'a>,
expr2: &Expr2,
size: &PhysicalSize<u32>,
position: Vector2<f32>,
glyph_brush: &mut GlyphBrush<()>,
) {
let area_bounds = (size.width as f32, size.height as f32).into();
match expr2 {
Expr2::SmallInt { text, .. } => {
let code_text = Text {
position,
area_bounds,
color: CODE_COL.into(),
text: env.pool.get_str(text),
size: CODE_FONT_SIZE,
..Default::default()
};
queue_code_text_draw(&code_text, glyph_brush);
}
Expr2::I128 { text, .. } => {
let code_text = Text {
position,
area_bounds,
color: CODE_COL.into(),
text: env.pool.get_str(text),
size: CODE_FONT_SIZE,
..Default::default()
};
queue_code_text_draw(&code_text, glyph_brush);
}
Expr2::U128 { text, .. } => {
let code_text = Text {
position,
area_bounds,
color: CODE_COL.into(),
text: env.pool.get_str(text),
size: CODE_FONT_SIZE,
..Default::default()
};
queue_code_text_draw(&code_text, glyph_brush);
}
Expr2::Float { text, .. } => {
let code_text = Text {
position,
area_bounds,
color: CODE_COL.into(),
text: env.pool.get_str(text),
size: CODE_FONT_SIZE,
..Default::default()
};
queue_code_text_draw(&code_text, glyph_brush);
}
Expr2::Str(text) => {
let code_text = Text {
position,
area_bounds,
color: CODE_COL.into(),
text: env.pool.get_str(text),
size: CODE_FONT_SIZE,
..Default::default()
};
queue_code_text_draw(&code_text, glyph_brush);
}
Expr2::GlobalTag { name, .. } => {
let code_text = Text {
position,
area_bounds,
color: CODE_COL.into(),
text: env.pool.get_str(name),
size: CODE_FONT_SIZE,
..Default::default()
};
queue_code_text_draw(&code_text, glyph_brush);
}
Expr2::Call { expr: expr_id, .. } => {
let expr = env.pool.get(*expr_id);
render_expr2(env, expr, size, position, glyph_brush);
}
Expr2::Var(symbol) => {
let text = format!("{:?}", symbol);
let code_text = Text {
position,
area_bounds,
color: CODE_COL.into(),
text: text.as_str(),
size: CODE_FONT_SIZE,
..Default::default()
};
queue_code_text_draw(&code_text, glyph_brush);
}
Expr2::List { elems, .. } => {
let code_text = Text {
position,
area_bounds,
color: CODE_COL.into(),
text: "[",
size: CODE_FONT_SIZE,
..Default::default()
};
queue_code_text_draw(&code_text, glyph_brush);
let mut x_pos = position.x;
for (idx, node_id) in elems.iter_node_ids().enumerate() {
let sub_expr2 = env.pool.get(node_id);
x_pos += 20.0;
render_expr2(
env,
sub_expr2,
size,
Vector2::new(x_pos, position.y),
glyph_brush,
);
if idx + 1 < elems.len() {
x_pos += 10.0;
let code_text = Text {
position: Vector2::new(x_pos, position.y),
area_bounds,
color: CODE_COL.into(),
text: ",",
size: CODE_FONT_SIZE,
..Default::default()
};
queue_code_text_draw(&code_text, glyph_brush);
}
}
x_pos += 20.0;
let code_text = Text {
position: Vector2::new(x_pos, position.y),
area_bounds,
color: CODE_COL.into(),
text: "]",
size: CODE_FONT_SIZE,
..Default::default()
};
queue_code_text_draw(&code_text, glyph_brush);
}
Expr2::Record { fields, .. } => {
let code_text = Text {
position,
area_bounds,
color: CODE_COL.into(),
text: "{",
size: CODE_FONT_SIZE,
..Default::default()
};
queue_code_text_draw(&code_text, glyph_brush);
let mut x_pos = position.x;
for (idx, node_id) in fields.iter_node_ids().enumerate() {
let (pool_field_name, _, sub_expr2_node_id) = env.pool.get(node_id);
let field_name = env.pool.get_str(pool_field_name);
x_pos += 20.0;
let code_text = Text {
position: Vector2::new(x_pos, position.y),
area_bounds,
color: CODE_COL.into(),
text: field_name,
size: CODE_FONT_SIZE,
..Default::default()
};
queue_code_text_draw(&code_text, glyph_brush);
x_pos += 10.0;
let code_text = Text {
position: Vector2::new(x_pos, position.y),
area_bounds,
color: CODE_COL.into(),
text: ":",
size: CODE_FONT_SIZE,
..Default::default()
};
queue_code_text_draw(&code_text, glyph_brush);
let sub_expr2 = env.pool.get(*sub_expr2_node_id);
x_pos += 20.0;
render_expr2(
env,
sub_expr2,
size,
Vector2::new(x_pos, position.y),
glyph_brush,
);
if idx + 1 < fields.len() {
x_pos += 10.0;
let code_text = Text {
position: Vector2::new(x_pos, position.y),
area_bounds,
color: CODE_COL.into(),
text: ",",
size: CODE_FONT_SIZE,
..Default::default()
};
queue_code_text_draw(&code_text, glyph_brush);
}
}
x_pos += 20.0;
let code_text = Text {
position: Vector2::new(x_pos, position.y),
area_bounds,
color: CODE_COL.into(),
text: "}",
size: CODE_FONT_SIZE,
..Default::default()
};
queue_code_text_draw(&code_text, glyph_brush);
}
rest => todo!("implement {:?} render", rest),
};
}

View file

@ -1,6 +1,7 @@
use crate::graphics::colors;
use crate::editor::colors as ed_colors;
use crate::graphics::colors as gr_colors;
use crate::graphics::primitives;
use colors::ColorTup;
use gr_colors::ColorTup;
//TODO optimize memory allocation
//TODO this is a demo function, the AST should be used for highlighting, see #904.
@ -10,16 +11,16 @@ pub fn highlight_code(
) {
let split_code = split_inclusive(&code_text.text);
let mut active_color = colors::WHITE;
let mut active_color = gr_colors::WHITE;
let mut same_type_str = String::new();
for token_seq in split_code {
let new_word_color = if token_seq.contains(&'\"'.to_string()) {
colors::CODE_COLOR
ed_colors::STRING_SYNTAX_COL
} else if token_seq.contains(&'='.to_string()) {
colors::BLACK
ed_colors::EQUALS_SYNTAX_COL
} else {
colors::WHITE
gr_colors::WHITE
};
if new_word_color != active_color {

View file

@ -1,11 +1,9 @@
use crate::error::EdResult;
use crate::error::OutOfBounds;
use super::ed_error::{EdResult, OutOfBounds};
use snafu::OptionExt;
use std::slice::SliceIndex;
// replace vec methods that return Option with ones that return Result and proper Error
pub fn get_res<T>(index: usize, slice: &[T]) -> EdResult<&<usize as SliceIndex<[T]>>::Output> {
pub fn slice_get<T>(index: usize, slice: &[T]) -> EdResult<&<usize as SliceIndex<[T]>>::Output> {
let elt_ref = slice.get(index).context(OutOfBounds {
index,
collection_name: "Slice",

View file

@ -1,11 +1,5 @@
pub type ColorTup = (f32, f32, f32, f32);
pub const WHITE: ColorTup = (1.0, 1.0, 1.0, 1.0);
pub const BLACK: ColorTup = (0.0, 0.0, 0.0, 1.0);
pub const TXT_COLOR: ColorTup = (1.0, 1.0, 1.0, 1.0);
pub const CODE_COLOR: ColorTup = (0.21, 0.55, 0.83, 1.0);
pub const CARET_COLOR: ColorTup = WHITE;
pub const SELECT_COLOR: ColorTup = (0.45, 0.61, 1.0, 1.0);
pub const BG_COLOR: ColorTup = (0.11, 0.11, 0.13, 1.0);
pub fn to_wgpu_color((r, g, b, a): ColorTup) -> wgpu::Color {
wgpu::Color {

View file

@ -4,7 +4,6 @@ use cgmath::Vector2;
#[derive(Copy, Clone)]
pub struct Vertex {
#[allow(dead_code)]
pub position: Vector2<f32>,
pub color: [f32; 4],
}

View file

@ -2,4 +2,3 @@ pub mod colors;
pub mod lowlevel;
pub mod primitives;
pub mod style;
mod syntax_highlight;

View file

@ -2,12 +2,12 @@
// by Benjamin Hansen, licensed under the MIT license
use super::rect::Rect;
use crate::editor::syntax_highlight;
use crate::graphics::colors;
use crate::graphics::colors::ColorTup;
use crate::graphics::style::{CODE_FONT_SIZE, CODE_TXT_XY};
use crate::graphics::syntax_highlight;
use ab_glyph::{FontArc, Glyph, InvalidFont};
use cgmath::{Vector2, Vector4};
use colors::{ColorTup, CODE_COLOR, WHITE};
use wgpu_glyph::{ab_glyph, GlyphBrush, GlyphBrushBuilder, GlyphCruncher, Section};
#[derive(Debug)]
@ -40,7 +40,7 @@ pub fn example_code_glyph_rect(glyph_brush: &mut GlyphBrush<()>) -> Rect {
let code_text = Text {
position: CODE_TXT_XY.into(),
area_bounds: (std::f32::INFINITY, std::f32::INFINITY).into(),
color: CODE_COLOR.into(),
color: (1.0, 1.0, 1.0, 1.0).into(),
text: "a",
size: CODE_FONT_SIZE,
..Default::default()
@ -145,7 +145,7 @@ fn glyph_to_rect(glyph: &wgpu_glyph::SectionGlyph) -> Rect {
top_left_coords: [position.x, top_y].into(),
width,
height,
color: WHITE,
color: colors::WHITE,
}
}

View file

@ -95,3 +95,39 @@ impl<'a> File<'a> {
self.fmt_then_write_to(self.path)
}
}
#[cfg(test)]
mod test_file {
use crate::lang::roc_file;
use bumpalo::Bump;
use std::path::Path;
#[test]
fn read_and_fmt_simple_roc_module() {
let simple_module_path = Path::new("./tests/modules/SimpleUnformatted.roc");
let arena = Bump::new();
let file = roc_file::File::read(simple_module_path, &arena)
.expect("Could not read SimpleUnformatted.roc in test_file test");
assert_eq!(
file.fmt(),
indoc!(
r#"
interface Simple
exposes [
v, x
]
imports []
v : Str
v = "Value!"
x : Int
x = 4"#
)
);
}
}

View file

@ -2,473 +2,20 @@
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)]
// Inspired by:
// https://github.com/sotrh/learn-wgpu by Benjamin Hansen, licensed under the MIT license
// https://github.com/cloudhead/rgx by Alexis Sellier, licensed under the MIT license
// See this link to learn wgpu: https://sotrh.github.io/learn-wgpu/
#[cfg_attr(test, macro_use)]
extern crate indoc;
extern crate pest;
#[cfg(test)]
#[macro_use]
#[cfg_attr(test, macro_use)]
extern crate pest_derive;
use crate::error::{print_err, EdResult};
use crate::graphics::colors::{CODE_COLOR, TXT_COLOR};
use crate::graphics::lowlevel::buffer::create_rect_buffers;
use crate::graphics::lowlevel::ortho::update_ortho_buffer;
use crate::graphics::lowlevel::pipelines;
use crate::graphics::primitives::text::{
build_glyph_brush, example_code_glyph_rect, queue_code_text_draw, queue_text_draw, Text,
};
use crate::graphics::style::CODE_FONT_SIZE;
use crate::graphics::style::CODE_TXT_XY;
use crate::mvc::app_model::AppModel;
use crate::mvc::ed_model::EdModel;
use crate::mvc::{app_update, ed_model, ed_view};
//use crate::resources::strings::NOTHING_OPENED;
use crate::vec_result::get_res;
use bumpalo::Bump;
use cgmath::Vector2;
use ed_model::Position;
use lang::{pool::Pool, scope::Scope};
use pipelines::RectResources;
use roc_module::symbol::{IdentIds, ModuleIds};
use roc_region::all::Region;
use roc_types::subs::VarStore;
use std::error::Error;
mod editor;
mod graphics;
pub mod lang; //TODO remove pub for unused warnings
mod ui;
use std::io;
use std::path::Path;
use wgpu::{CommandEncoder, RenderPass, TextureView};
use wgpu_glyph::GlyphBrush;
use winit::dpi::PhysicalSize;
use winit::event;
use winit::event::{Event, ModifiersState};
use winit::event_loop::ControlFlow;
pub mod error;
pub mod graphics;
mod keyboard_input;
pub mod lang;
mod mvc;
//pub mod mvc; // for benchmarking
mod resources;
mod selection;
mod text_buffer;
//pub mod text_buffer; // for benchmarking
mod render;
mod util;
mod vec_result;
/// The editor is actually launched from the CLI if you pass it zero arguments,
/// or if you provide it 1 or more files or directories to open on launch.
pub fn launch(filepaths: &[&Path]) -> io::Result<()> {
//TODO support using multiple filepaths
let first_path_opt = if !filepaths.is_empty() {
match get_res(0, filepaths) {
Ok(path_ref_ref) => Some(*path_ref_ref),
Err(e) => {
eprintln!("{}", e);
None
}
}
} else {
None
};
run_event_loop(first_path_opt).expect("Error running event loop");
Ok(())
}
fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
env_logger::init();
// Open window and create a surface
let event_loop = winit::event_loop::EventLoop::new();
let window = winit::window::WindowBuilder::new()
.with_inner_size(PhysicalSize::new(1200.0, 1000.0))
.build(&event_loop)
.unwrap();
let instance = wgpu::Instance::new(wgpu::BackendBit::all());
let surface = unsafe { instance.create_surface(&window) };
// Initialize GPU
let (gpu_device, cmd_queue) = futures::executor::block_on(async {
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface: Some(&surface),
})
.await
.expect("Request adapter");
adapter
.request_device(
&wgpu::DeviceDescriptor {
features: wgpu::Features::empty(),
limits: wgpu::Limits::default(),
shader_validation: false,
},
None,
)
.await
.expect("Request device")
});
// Create staging belt and a local pool
let mut staging_belt = wgpu::util::StagingBelt::new(1024);
let mut local_pool = futures::executor::LocalPool::new();
let local_spawner = local_pool.spawner();
// Prepare swap chain
let render_format = wgpu::TextureFormat::Bgra8Unorm;
let mut size = window.inner_size();
let swap_chain_descr = wgpu::SwapChainDescriptor {
usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT,
format: render_format,
width: size.width,
height: size.height,
//Immediate may cause tearing, change present_mode if this becomes a problem
present_mode: wgpu::PresentMode::Immediate,
};
let mut swap_chain = gpu_device.create_swap_chain(&surface, &swap_chain_descr);
let rect_resources = pipelines::make_rect_pipeline(&gpu_device, &swap_chain_descr);
let mut glyph_brush = build_glyph_brush(&gpu_device, render_format)?;
let is_animating = true;
let ed_model_opt = if let Some(file_path) = file_path_opt {
let ed_model_res = ed_model::init_model(file_path);
match ed_model_res {
Ok(mut ed_model) => {
ed_model.glyph_dim_rect_opt = Some(example_code_glyph_rect(&mut glyph_brush));
Some(ed_model)
}
Err(e) => {
print_err(&e);
None
}
}
} else {
None
};
let mut app_model = AppModel::init(ed_model_opt);
let mut keyboard_modifiers = ModifiersState::empty();
// This arena is never cleared and should only be used for allocations that occur rarely
let arena = Bump::new();
let mut rects_arena = Bump::new();
// Render loop
window.request_redraw();
event_loop.run(move |event, _, control_flow| {
// TODO dynamically switch this on/off depending on whether any
// animations are running. Should conserve CPU usage and battery life!
if is_animating {
*control_flow = ControlFlow::Poll;
} else {
*control_flow = ControlFlow::Wait;
}
match event {
//Close
Event::WindowEvent {
event: event::WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
//Resize
Event::WindowEvent {
event: event::WindowEvent::Resized(new_size),
..
} => {
size = new_size;
swap_chain = gpu_device.create_swap_chain(
&surface,
&wgpu::SwapChainDescriptor {
usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT,
format: render_format,
width: size.width,
height: size.height,
//Immediate may cause tearing, change present_mode if this becomes a problem
present_mode: wgpu::PresentMode::Immediate,
},
);
update_ortho_buffer(
size.width,
size.height,
&gpu_device,
&rect_resources.ortho.buffer,
&cmd_queue,
);
}
//Received Character
Event::WindowEvent {
event: event::WindowEvent::ReceivedCharacter(ch),
..
} => {
if let Err(e) = app_update::handle_new_char(&ch, &mut app_model) {
print_err(&e)
}
}
//Keyboard Input
Event::WindowEvent {
event: event::WindowEvent::KeyboardInput { input, .. },
..
} => {
if let Some(virtual_keycode) = input.virtual_keycode {
if let Some(ref mut ed_model) = app_model.ed_model_opt {
if ed_model.has_focus {
let keydown_res = keyboard_input::handle_keydown(
input.state,
virtual_keycode,
keyboard_modifiers,
&mut app_model,
);
if let Err(e) = keydown_res {
print_err(&e)
}
}
}
}
}
//Modifiers Changed
Event::WindowEvent {
event: event::WindowEvent::ModifiersChanged(modifiers),
..
} => {
keyboard_modifiers = modifiers;
}
Event::MainEventsCleared => window.request_redraw(),
Event::RedrawRequested { .. } => {
// Get a command encoder for the current frame
let mut encoder =
gpu_device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Redraw"),
});
let frame = swap_chain
.get_current_frame()
.expect("Failed to acquire next SwapChainFrame")
.output;
if let Some(ed_model) = &app_model.ed_model_opt {
//TODO don't pass invisible lines
queue_editor_text(
&size,
&ed_model.text_buf.all_lines(&arena),
ed_model.caret_pos,
CODE_TXT_XY.into(),
&mut glyph_brush,
);
} else {
// queue_no_file_text(&size, NOTHING_OPENED, CODE_TXT_XY.into(), &mut glyph_brush);
let mut pool = Pool::with_capacity(1024);
let mut var_store = VarStore::default();
let dep_idents = IdentIds::exposed_builtins(8);
let mut module_ids = ModuleIds::default();
let exposed_ident_ids = IdentIds::default();
let home = module_ids.get_or_insert(&"Home".into());
let mut env = crate::lang::expr::Env::new(
home,
&arena,
&mut pool,
&mut var_store,
dep_idents,
&module_ids,
exposed_ident_ids,
);
let mut scope = Scope::new(home, env.pool, env.var_store);
let region = Region::new(0, 0, 0, 0);
let (expr2, _) = crate::lang::expr::str_to_expr2(
&arena,
"Num.add 1 1",
&mut env,
&mut scope,
region,
)
.unwrap();
render::render_expr2(
&mut env,
&expr2,
&size,
CODE_TXT_XY.into(),
&mut glyph_brush,
);
}
rects_arena.reset();
match draw_all_rects(
&app_model.ed_model_opt,
&rects_arena,
&mut encoder,
&frame.view,
&gpu_device,
&rect_resources,
) {
Ok(()) => (),
Err(e) => {
print_err(&e);
begin_render_pass(&mut encoder, &frame.view);
}
}
// draw all text
glyph_brush
.draw_queued(
&gpu_device,
&mut staging_belt,
&mut encoder,
&frame.view,
size.width,
size.height,
)
.expect("Draw queued");
staging_belt.finish();
cmd_queue.submit(Some(encoder.finish()));
// Recall unused staging buffers
use futures::task::SpawnExt;
local_spawner
.spawn(staging_belt.recall())
.expect("Recall staging belt");
local_pool.run_until_stalled();
}
_ => {
*control_flow = winit::event_loop::ControlFlow::Wait;
}
}
})
}
fn draw_all_rects(
ed_model_opt: &Option<EdModel>,
arena: &Bump,
encoder: &mut CommandEncoder,
texture_view: &TextureView,
gpu_device: &wgpu::Device,
rect_resources: &RectResources,
) -> EdResult<()> {
if let Some(ed_model) = ed_model_opt {
let all_rects = ed_view::create_ed_rects(ed_model, arena)?;
let rect_buffers = create_rect_buffers(gpu_device, encoder, &all_rects);
let mut render_pass = begin_render_pass(encoder, texture_view);
render_pass.set_pipeline(&rect_resources.pipeline);
render_pass.set_bind_group(0, &rect_resources.ortho.bind_group, &[]);
render_pass.set_vertex_buffer(0, rect_buffers.vertex_buffer.slice(..));
render_pass.set_index_buffer(rect_buffers.index_buffer.slice(..));
render_pass.draw_indexed(0..rect_buffers.num_rects, 0, 0..1);
} else {
// need to begin render pass to clear screen
begin_render_pass(encoder, texture_view);
}
Ok(())
}
fn begin_render_pass<'a>(
encoder: &'a mut CommandEncoder,
texture_view: &'a TextureView,
) -> RenderPass<'a> {
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
attachment: texture_view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
r: 0.1,
g: 0.2,
b: 0.3,
a: 1.0,
}),
store: true,
},
}],
depth_stencil_attachment: None,
})
}
// returns bounding boxes for every glyph
fn queue_editor_text(
size: &PhysicalSize<u32>,
editor_lines: &str,
caret_pos: Position,
code_coords: Vector2<f32>,
glyph_brush: &mut GlyphBrush<()>,
) {
let area_bounds = (size.width as f32, size.height as f32).into();
let code_text = Text {
position: code_coords,
area_bounds,
color: CODE_COLOR.into(),
text: editor_lines,
size: CODE_FONT_SIZE,
..Default::default()
};
let s = format!("Ln {}, Col {}", caret_pos.line, caret_pos.column);
let text = s.as_str();
let caret_pos_label = Text {
position: ((size.width as f32) - 150.0, (size.height as f32) - 40.0).into(),
area_bounds,
color: TXT_COLOR.into(),
text,
size: 25.0,
..Default::default()
};
queue_text_draw(&caret_pos_label, glyph_brush);
queue_code_text_draw(&code_text, glyph_brush);
}
fn _queue_no_file_text(
size: &PhysicalSize<u32>,
text: &str,
text_coords: Vector2<f32>,
glyph_brush: &mut GlyphBrush<()>,
) {
let area_bounds = (size.width as f32, size.height as f32).into();
let code_text = Text {
position: text_coords,
area_bounds,
color: CODE_COLOR.into(),
text,
size: CODE_FONT_SIZE,
..Default::default()
};
queue_text_draw(&code_text, glyph_brush);
editor::main::launch(filepaths)
}

View file

@ -1,91 +0,0 @@
use crate::error::EdResult;
use crate::graphics::primitives::rect::Rect;
use crate::text_buffer;
use crate::text_buffer::TextBuffer;
use std::cmp::Ordering;
use std::fmt;
use std::path::Path;
#[derive(Debug)]
pub struct EdModel {
pub text_buf: TextBuffer,
pub caret_pos: Position,
pub selection_opt: Option<RawSelection>,
pub glyph_dim_rect_opt: Option<Rect>,
pub has_focus: bool,
}
pub fn init_model(file_path: &Path) -> EdResult<EdModel> {
Ok(EdModel {
text_buf: text_buffer::from_path(file_path)?,
caret_pos: Position { line: 0, column: 0 },
selection_opt: None,
glyph_dim_rect_opt: None,
has_focus: true,
})
}
impl EdModel {
pub fn get_selected_str(&self) -> EdResult<Option<&str>> {
if let Some(curr_selection) = self.selection_opt {
let selected_str = self.text_buf.get_selection(curr_selection)?;
Ok(Some(selected_str))
} else {
Ok(None)
}
}
pub fn del_selection(&mut self) -> EdResult<()> {
if let Some(selection) = self.selection_opt {
self.text_buf.del_selection(selection)?;
self.caret_pos = selection.start_pos;
}
self.selection_opt = None;
Ok(())
}
}
#[derive(Debug, Copy, Clone)]
pub struct Position {
pub line: usize,
pub column: usize,
}
impl Ord for Position {
fn cmp(&self, other: &Self) -> Ordering {
(self.line, self.column).cmp(&(other.line, other.column))
}
}
impl PartialOrd for Position {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for Position {
fn eq(&self, other: &Self) -> bool {
(self.line, self.column) == (other.line, other.column)
}
}
impl Eq for Position {}
#[derive(Debug, Copy, Clone)]
pub struct RawSelection {
pub start_pos: Position,
pub end_pos: Position,
}
impl std::fmt::Display for RawSelection {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"RawSelection: start_pos: line:{} col:{}, end_pos: line:{} col:{}",
self.start_pos.line, self.start_pos.column, self.end_pos.line, self.end_pos.column
)
}
}

View file

@ -1,625 +0,0 @@
use super::ed_model::EdModel;
use super::ed_model::{Position, RawSelection};
use crate::error::EdResult;
use crate::text_buffer::TextBuffer;
use crate::util::is_newline;
use std::cmp::{max, min};
use winit::event::VirtualKeyCode::*;
use winit::event::{ModifiersState, VirtualKeyCode};
pub type MoveCaretFun =
fn(Position, Option<RawSelection>, bool, &TextBuffer) -> (Position, Option<RawSelection>);
pub fn move_caret_left(
old_caret_pos: Position,
old_selection_opt: Option<RawSelection>,
shift_pressed: bool,
text_buf: &TextBuffer,
) -> (Position, Option<RawSelection>) {
let old_line_nr = old_caret_pos.line;
let old_col_nr = old_caret_pos.column;
let (line_nr, col_nr) = if old_selection_opt.is_some() && !shift_pressed {
match old_selection_opt {
Some(old_selection) => (old_selection.start_pos.line, old_selection.start_pos.column),
None => unreachable!(),
}
} else if old_col_nr == 0 {
if old_line_nr == 0 {
(0, 0)
} else if let Some(curr_line_len) = text_buf.line_len(old_line_nr - 1) {
(old_line_nr - 1, curr_line_len - 1)
} else {
unreachable!()
}
} else {
(old_line_nr, old_col_nr - 1)
};
let new_caret_pos = Position {
line: line_nr,
column: col_nr,
};
let new_selection_opt = if shift_pressed {
if let Some(old_selection) = old_selection_opt {
if old_caret_pos >= old_selection.end_pos {
if new_caret_pos == old_selection.start_pos {
None
} else {
Some(RawSelection {
start_pos: old_selection.start_pos,
end_pos: new_caret_pos,
})
}
} else {
Some(RawSelection {
start_pos: Position {
line: line_nr,
column: col_nr,
},
end_pos: old_selection.end_pos,
})
}
} else if !(old_line_nr == line_nr && old_col_nr == col_nr) {
Some(RawSelection {
start_pos: Position {
line: line_nr,
column: col_nr,
},
end_pos: Position {
line: old_line_nr,
column: old_col_nr,
},
})
} else {
None
}
} else {
None
};
(new_caret_pos, new_selection_opt)
}
pub fn move_caret_right(
old_caret_pos: Position,
old_selection_opt: Option<RawSelection>,
shift_pressed: bool,
text_buf: &TextBuffer,
) -> (Position, Option<RawSelection>) {
let old_line_nr = old_caret_pos.line;
let old_col_nr = old_caret_pos.column;
let (line_nr, col_nr) = if old_selection_opt.is_some() && !shift_pressed {
match old_selection_opt {
Some(old_selection) => (old_selection.end_pos.line, old_selection.end_pos.column),
None => unreachable!(),
}
} else if let Some(curr_line) = text_buf.line(old_line_nr) {
if let Some(last_char) = curr_line.chars().last() {
if is_newline(&last_char) {
if old_col_nr + 1 > curr_line.len() - 1 {
(old_line_nr + 1, 0)
} else {
(old_line_nr, old_col_nr + 1)
}
} else if old_col_nr < curr_line.len() {
(old_line_nr, old_col_nr + 1)
} else {
(old_line_nr, old_col_nr)
}
} else {
(old_line_nr, old_col_nr)
}
} else {
unreachable!()
};
let new_caret_pos = Position {
line: line_nr,
column: col_nr,
};
let new_selection_opt = if shift_pressed {
if let Some(old_selection) = old_selection_opt {
if old_caret_pos <= old_selection.start_pos {
if new_caret_pos == old_selection.end_pos {
None
} else {
Some(RawSelection {
start_pos: new_caret_pos,
end_pos: old_selection.end_pos,
})
}
} else {
Some(RawSelection {
start_pos: old_selection.start_pos,
end_pos: Position {
line: line_nr,
column: col_nr,
},
})
}
} else if !(old_line_nr == line_nr && old_col_nr == col_nr) {
Some(RawSelection {
start_pos: Position {
line: old_line_nr,
column: old_col_nr,
},
end_pos: Position {
line: line_nr,
column: col_nr,
},
})
} else {
None
}
} else {
None
};
(new_caret_pos, new_selection_opt)
}
pub fn move_caret_up(
old_caret_pos: Position,
old_selection_opt: Option<RawSelection>,
shift_pressed: bool,
text_buf: &TextBuffer,
) -> (Position, Option<RawSelection>) {
let old_line_nr = old_caret_pos.line;
let old_col_nr = old_caret_pos.column;
let (line_nr, col_nr) = if old_selection_opt.is_some() && !shift_pressed {
match old_selection_opt {
Some(old_selection) => (old_selection.start_pos.line, old_selection.start_pos.column),
None => unreachable!(),
}
} else if old_line_nr == 0 {
(old_line_nr, 0)
} else if let Some(prev_line_len) = text_buf.line_len(old_line_nr - 1) {
if prev_line_len <= old_col_nr {
(old_line_nr - 1, prev_line_len - 1)
} else {
(old_line_nr - 1, old_col_nr)
}
} else {
unreachable!()
};
let new_caret_pos = Position {
line: line_nr,
column: col_nr,
};
let new_selection_opt = if shift_pressed {
if let Some(old_selection) = old_selection_opt {
if old_selection.end_pos <= old_caret_pos {
if new_caret_pos == old_selection.start_pos {
None
} else {
Some(RawSelection {
start_pos: min(old_selection.start_pos, new_caret_pos),
end_pos: max(old_selection.start_pos, new_caret_pos),
})
}
} else {
Some(RawSelection {
start_pos: new_caret_pos,
end_pos: old_selection.end_pos,
})
}
} else if !(old_line_nr == line_nr && old_col_nr == col_nr) {
Some(RawSelection {
start_pos: min(old_caret_pos, new_caret_pos),
end_pos: max(old_caret_pos, new_caret_pos),
})
} else {
None
}
} else {
None
};
(new_caret_pos, new_selection_opt)
}
pub fn move_caret_down(
old_caret_pos: Position,
old_selection_opt: Option<RawSelection>,
shift_pressed: bool,
text_buf: &TextBuffer,
) -> (Position, Option<RawSelection>) {
let old_line_nr = old_caret_pos.line;
let old_col_nr = old_caret_pos.column;
let (line_nr, col_nr) = if old_selection_opt.is_some() && !shift_pressed {
match old_selection_opt {
Some(old_selection) => (old_selection.end_pos.line, old_selection.end_pos.column),
None => unreachable!(),
}
} else if old_line_nr + 1 >= text_buf.nr_of_lines() {
if let Some(curr_line_len) = text_buf.line_len(old_line_nr) {
(old_line_nr, curr_line_len)
} else {
unreachable!()
}
} else if let Some(next_line) = text_buf.line(old_line_nr + 1) {
if next_line.len() <= old_col_nr {
if let Some(last_char) = next_line.chars().last() {
if is_newline(&last_char) {
(old_line_nr + 1, next_line.len() - 1)
} else {
(old_line_nr + 1, next_line.len())
}
} else {
(old_line_nr + 1, 0)
}
} else {
(old_line_nr + 1, old_col_nr)
}
} else {
unreachable!()
};
let new_caret_pos = Position {
line: line_nr,
column: col_nr,
};
let new_selection_opt = if shift_pressed {
if let Some(old_selection) = old_selection_opt {
if old_caret_pos <= old_selection.start_pos {
if new_caret_pos == old_selection.end_pos {
None
} else {
Some(RawSelection {
start_pos: min(old_selection.end_pos, new_caret_pos),
end_pos: max(old_selection.end_pos, new_caret_pos),
})
}
} else {
Some(RawSelection {
start_pos: old_selection.start_pos,
end_pos: new_caret_pos,
})
}
} else if !(old_line_nr == line_nr && old_col_nr == col_nr) {
Some(RawSelection {
start_pos: min(old_caret_pos, new_caret_pos),
end_pos: max(old_caret_pos, new_caret_pos),
})
} else {
None
}
} else {
None
};
(new_caret_pos, new_selection_opt)
}
fn handle_arrow(move_caret_fun: MoveCaretFun, modifiers: &ModifiersState, ed_model: &mut EdModel) {
let (new_caret_pos, new_selection_opt) = move_caret_fun(
ed_model.caret_pos,
ed_model.selection_opt,
modifiers.shift(),
&ed_model.text_buf,
);
ed_model.caret_pos = new_caret_pos;
ed_model.selection_opt = new_selection_opt;
}
fn del_selection(selection: RawSelection, ed_model: &mut EdModel) -> EdResult<()> {
ed_model.text_buf.del_selection(selection)?;
ed_model.caret_pos = selection.start_pos;
Ok(())
}
// TODO move this to impl EdModel
pub fn handle_select_all(ed_model: &mut EdModel) {
if ed_model.text_buf.nr_of_chars() > 0 {
let last_pos = ed_model.text_buf.last_position();
ed_model.selection_opt = Some(RawSelection {
start_pos: Position { line: 0, column: 0 },
end_pos: last_pos,
});
ed_model.caret_pos = last_pos;
}
}
pub fn handle_new_char(received_char: &char, ed_model: &mut EdModel) -> EdResult<()> {
let old_caret_pos = ed_model.caret_pos;
match received_char {
'\u{8}' | '\u{7f}' => {
// On Linux, '\u{8}' is backspace,
// on macOS '\u{7f}'.
if let Some(selection) = ed_model.selection_opt {
del_selection(selection, ed_model)?;
} else {
ed_model.caret_pos =
move_caret_left(old_caret_pos, None, false, &ed_model.text_buf).0;
ed_model.text_buf.pop_char(old_caret_pos);
}
ed_model.selection_opt = None;
}
ch if is_newline(ch) => {
if let Some(selection) = ed_model.selection_opt {
del_selection(selection, ed_model)?;
ed_model.text_buf.insert_char(ed_model.caret_pos, &'\n')?;
} else {
ed_model.text_buf.insert_char(old_caret_pos, &'\n')?;
ed_model.caret_pos = Position {
line: old_caret_pos.line + 1,
column: 0,
};
}
ed_model.selection_opt = None;
}
'\u{1}' // Ctrl + A
| '\u{3}' // Ctrl + C
| '\u{16}' // Ctrl + V
| '\u{18}' // Ctrl + X
| '\u{e000}'..='\u{f8ff}' // http://www.unicode.org/faq/private_use.html
| '\u{f0000}'..='\u{ffffd}' // ^
| '\u{100000}'..='\u{10fffd}' // ^
=> {
// chars that can be ignored
}
_ => {
if let Some(selection) = ed_model.selection_opt {
del_selection(selection, ed_model)?;
ed_model
.text_buf
.insert_char(ed_model.caret_pos, received_char)?;
ed_model.caret_pos =
move_caret_right(ed_model.caret_pos, None, false, &ed_model.text_buf).0;
} else {
ed_model
.text_buf
.insert_char(old_caret_pos, received_char)?;
ed_model.caret_pos = Position {
line: old_caret_pos.line,
column: old_caret_pos.column + 1,
};
}
ed_model.selection_opt = None;
}
}
Ok(())
}
pub fn handle_key_down(
modifiers: &ModifiersState,
virtual_keycode: VirtualKeyCode,
ed_model: &mut EdModel,
) {
match virtual_keycode {
Left => handle_arrow(move_caret_left, modifiers, ed_model),
Up => handle_arrow(move_caret_up, modifiers, ed_model),
Right => handle_arrow(move_caret_right, modifiers, ed_model),
Down => handle_arrow(move_caret_down, modifiers, ed_model),
A => {
if modifiers.ctrl() {
handle_select_all(ed_model)
}
}
_ => {}
}
}
#[cfg(test)]
pub mod test_ed_update {
use crate::mvc::app_update::test_app_update::mock_app_model;
use crate::mvc::ed_model::{Position, RawSelection};
use crate::mvc::ed_update::{handle_new_char, handle_select_all};
use crate::selection::test_selection::{
all_lines_vec, convert_dsl_to_selection, convert_selection_to_dsl, text_buffer_from_dsl_str,
};
use crate::text_buffer::TextBuffer;
pub fn gen_caret_text_buf(
lines: &[&str],
) -> Result<(Position, Option<RawSelection>, TextBuffer), String> {
let lines_string_slice: Vec<String> = lines.iter().map(|l| l.to_string()).collect();
let (selection_opt, caret_pos) = convert_dsl_to_selection(&lines_string_slice)?;
let text_buf = text_buffer_from_dsl_str(&lines_string_slice);
Ok((caret_pos, selection_opt, text_buf))
}
fn assert_insert(
pre_lines_str: &[&str],
expected_post_lines_str: &[&str],
new_char: char,
) -> Result<(), String> {
let (caret_pos, selection_opt, pre_text_buf) = gen_caret_text_buf(pre_lines_str)?;
let app_model = mock_app_model(pre_text_buf, caret_pos, selection_opt, None);
let mut ed_model = app_model.ed_model_opt.unwrap();
if let Err(e) = handle_new_char(&new_char, &mut ed_model) {
return Err(e.to_string());
}
let mut actual_lines = all_lines_vec(&ed_model.text_buf);
let dsl_slice = convert_selection_to_dsl(
ed_model.selection_opt,
ed_model.caret_pos,
&mut actual_lines,
)
.unwrap();
assert_eq!(dsl_slice, expected_post_lines_str);
Ok(())
}
#[test]
fn insert_new_char_simple() -> Result<(), String> {
assert_insert(&["|"], &["a|"], 'a')?;
assert_insert(&["|"], &[" |"], ' ')?;
assert_insert(&["a|"], &["aa|"], 'a')?;
assert_insert(&["a|"], &["a |"], ' ')?;
assert_insert(&["a|\n", ""], &["ab|\n", ""], 'b')?;
assert_insert(&["a|\n", ""], &["ab|\n", ""], 'b')?;
assert_insert(&["a\n", "|"], &["a\n", "b|"], 'b')?;
assert_insert(&["a\n", "b\n", "c|"], &["a\n", "b\n", "cd|"], 'd')?;
Ok(())
}
#[test]
fn insert_new_char_mid() -> Result<(), String> {
assert_insert(&["ab|d"], &["abc|d"], 'c')?;
assert_insert(&["a|cd"], &["ab|cd"], 'b')?;
assert_insert(&["abc\n", "|e"], &["abc\n", "d|e"], 'd')?;
assert_insert(&["abc\n", "def\n", "| "], &["abc\n", "def\n", "g| "], 'g')?;
assert_insert(&["abc\n", "def\n", "| "], &["abc\n", "def\n", " | "], ' ')?;
Ok(())
}
#[test]
fn simple_backspace() -> Result<(), String> {
assert_insert(&["|"], &["|"], '\u{8}')?;
assert_insert(&[" |"], &["|"], '\u{8}')?;
assert_insert(&["a|"], &["|"], '\u{8}')?;
assert_insert(&["ab|"], &["a|"], '\u{8}')?;
assert_insert(&["a|\n", ""], &["|\n", ""], '\u{8}')?;
assert_insert(&["ab|\n", ""], &["a|\n", ""], '\u{8}')?;
assert_insert(&["a\n", "|"], &["a|"], '\u{8}')?;
assert_insert(&["a\n", "b\n", "c|"], &["a\n", "b\n", "|"], '\u{8}')?;
assert_insert(&["a\n", "b\n", "|"], &["a\n", "b|"], '\u{8}')?;
Ok(())
}
#[test]
fn selection_backspace() -> Result<(), String> {
assert_insert(&["[a]|"], &["|"], '\u{8}')?;
assert_insert(&["a[a]|"], &["a|"], '\u{8}')?;
assert_insert(&["[aa]|"], &["|"], '\u{8}')?;
assert_insert(&["a[b c]|"], &["a|"], '\u{8}')?;
assert_insert(&["[abc]|\n", ""], &["|\n", ""], '\u{8}')?;
assert_insert(&["a\n", "[abc]|"], &["a\n", "|"], '\u{8}')?;
assert_insert(&["[a\n", "abc]|"], &["|"], '\u{8}')?;
assert_insert(&["a[b\n", "cdef ghij]|"], &["a|"], '\u{8}')?;
assert_insert(&["[a\n", "b\n", "c]|"], &["|"], '\u{8}')?;
assert_insert(&["a\n", "[b\n", "]|"], &["a\n", "|"], '\u{8}')?;
assert_insert(
&["abc\n", "d[ef\n", "ghi]|\n", "jkl"],
&["abc\n", "d|\n", "jkl"],
'\u{8}',
)?;
assert_insert(
&["abc\n", "[def\n", "ghi]|\n", "jkl"],
&["abc\n", "|\n", "jkl"],
'\u{8}',
)?;
assert_insert(
&["abc\n", "\n", "[def\n", "ghi]|\n", "jkl"],
&["abc\n", "\n", "|\n", "jkl"],
'\u{8}',
)?;
assert_insert(
&["[abc\n", "\n", "def\n", "ghi\n", "jkl]|"],
&["|"],
'\u{8}',
)?;
Ok(())
}
#[test]
fn insert_with_selection() -> Result<(), String> {
assert_insert(&["[a]|"], &["z|"], 'z')?;
assert_insert(&["a[a]|"], &["az|"], 'z')?;
assert_insert(&["[aa]|"], &["z|"], 'z')?;
assert_insert(&["a[b c]|"], &["az|"], 'z')?;
assert_insert(&["[abc]|\n", ""], &["z|\n", ""], 'z')?;
assert_insert(&["a\n", "[abc]|"], &["a\n", "z|"], 'z')?;
assert_insert(&["[a\n", "abc]|"], &["z|"], 'z')?;
assert_insert(&["a[b\n", "cdef ghij]|"], &["az|"], 'z')?;
assert_insert(&["[a\n", "b\n", "c]|"], &["z|"], 'z')?;
assert_insert(&["a\n", "[b\n", "]|"], &["a\n", "z|"], 'z')?;
assert_insert(
&["abc\n", "d[ef\n", "ghi]|\n", "jkl"],
&["abc\n", "dz|\n", "jkl"],
'z',
)?;
assert_insert(
&["abc\n", "[def\n", "ghi]|\n", "jkl"],
&["abc\n", "z|\n", "jkl"],
'z',
)?;
assert_insert(
&["abc\n", "\n", "[def\n", "ghi]|\n", "jkl"],
&["abc\n", "\n", "z|\n", "jkl"],
'z',
)?;
assert_insert(&["[abc\n", "\n", "def\n", "ghi\n", "jkl]|"], &["z|"], 'z')?;
Ok(())
}
fn assert_select_all(
pre_lines_str: &[&str],
expected_post_lines_str: &[&str],
) -> Result<(), String> {
let (caret_pos, selection_opt, pre_text_buf) = gen_caret_text_buf(pre_lines_str)?;
let app_model = mock_app_model(pre_text_buf, caret_pos, selection_opt, None);
let mut ed_model = app_model.ed_model_opt.unwrap();
handle_select_all(&mut ed_model);
let mut text_buf_lines = all_lines_vec(&ed_model.text_buf);
let post_lines_str = convert_selection_to_dsl(
ed_model.selection_opt,
ed_model.caret_pos,
&mut text_buf_lines,
)?;
assert_eq!(post_lines_str, expected_post_lines_str);
Ok(())
}
#[test]
fn select_all() -> Result<(), String> {
assert_select_all(&["|"], &["|"])?;
assert_select_all(&["|a"], &["[a]|"])?;
assert_select_all(&["a|"], &["[a]|"])?;
assert_select_all(&["abc d|ef ghi"], &["[abc def ghi]|"])?;
assert_select_all(&["[a]|"], &["[a]|"])?;
assert_select_all(&["|[a]"], &["[a]|"])?;
assert_select_all(&["|[abc def ghi]"], &["[abc def ghi]|"])?;
assert_select_all(&["a\n", "[b\n", "]|"], &["[a\n", "b\n", "]|"])?;
assert_select_all(&["a\n", "[b]|\n", ""], &["[a\n", "b\n", "]|"])?;
assert_select_all(&["a\n", "|[b\n", "]"], &["[a\n", "b\n", "]|"])?;
assert_select_all(
&["abc\n", "def\n", "gh|i\n", "jkl"],
&["[abc\n", "def\n", "ghi\n", "jkl]|"],
)?;
assert_select_all(
&["|[abc\n", "def\n", "ghi\n", "jkl]"],
&["[abc\n", "def\n", "ghi\n", "jkl]|"],
)?;
Ok(())
}
}

View file

@ -1,117 +0,0 @@
use cgmath::Vector2;
use wgpu_glyph::GlyphBrush;
use winit::dpi::PhysicalSize;
use crate::{
graphics::{
colors::CODE_COLOR,
primitives::text::{queue_code_text_draw, Text},
style::CODE_FONT_SIZE,
},
lang::{ast::Expr2, expr::Env},
};
pub fn render_expr2<'a>(
env: &mut Env<'a>,
expr2: &Expr2,
size: &PhysicalSize<u32>,
position: Vector2<f32>,
glyph_brush: &mut GlyphBrush<()>,
) {
let area_bounds = (size.width as f32, size.height as f32).into();
match expr2 {
Expr2::SmallInt { text, .. } => {
let code_text = Text {
position,
area_bounds,
color: CODE_COLOR.into(),
text: env.pool.get_str(text),
size: CODE_FONT_SIZE,
..Default::default()
};
queue_code_text_draw(&code_text, glyph_brush);
}
Expr2::I128 { text, .. } => {
let code_text = Text {
position,
area_bounds,
color: CODE_COLOR.into(),
text: env.pool.get_str(text),
size: CODE_FONT_SIZE,
..Default::default()
};
queue_code_text_draw(&code_text, glyph_brush);
}
Expr2::U128 { text, .. } => {
let code_text = Text {
position,
area_bounds,
color: CODE_COLOR.into(),
text: env.pool.get_str(text),
size: CODE_FONT_SIZE,
..Default::default()
};
queue_code_text_draw(&code_text, glyph_brush);
}
Expr2::Float { text, .. } => {
let code_text = Text {
position,
area_bounds,
color: CODE_COLOR.into(),
text: env.pool.get_str(text),
size: CODE_FONT_SIZE,
..Default::default()
};
queue_code_text_draw(&code_text, glyph_brush);
}
Expr2::Str(text) => {
let code_text = Text {
position,
area_bounds,
color: CODE_COLOR.into(),
text: env.pool.get_str(text),
size: CODE_FONT_SIZE,
..Default::default()
};
queue_code_text_draw(&code_text, glyph_brush);
}
Expr2::GlobalTag { name, .. } => {
let code_text = Text {
position,
area_bounds,
color: CODE_COLOR.into(),
text: env.pool.get_str(name),
size: CODE_FONT_SIZE,
..Default::default()
};
queue_code_text_draw(&code_text, glyph_brush);
}
Expr2::Call { expr: expr_id, .. } => {
let expr = env.pool.get(*expr_id);
render_expr2(env, expr, size, position, glyph_brush);
}
Expr2::Var(symbol) => {
let text = format!("{:?}", symbol);
let code_text = Text {
position,
area_bounds,
color: CODE_COLOR.into(),
text: text.as_str(),
size: CODE_FONT_SIZE,
..Default::default()
};
queue_code_text_draw(&code_text, glyph_brush);
}
rest => todo!("implement {:?} render", rest),
};
}

View file

@ -1,207 +0,0 @@
// Adapted from https://github.com/cessen/ropey by Nathan Vegdahl, licensed under the MIT license
use crate::error::EdError::{FileOpenFailed, TextBufReadFailed};
use crate::error::EdResult;
use crate::error::OutOfBounds;
use crate::mvc::ed_model::{Position, RawSelection};
use crate::selection::validate_selection;
use bumpalo::collections::String as BumpString;
use bumpalo::Bump;
use ropey::Rope;
use snafu::{ensure, OptionExt};
use std::fmt;
use std::fs::File;
use std::io;
use std::path::Path;
pub struct TextBuffer {
pub text_rope: Rope,
pub path_str: String,
pub arena: Bump,
}
impl TextBuffer {
pub fn insert_char(&mut self, caret_pos: Position, new_char: &char) -> EdResult<()> {
self.insert_str(caret_pos, &new_char.to_string())
}
pub fn insert_str(&mut self, caret_pos: Position, new_str: &str) -> EdResult<()> {
let char_indx = self.pos_to_char_indx(caret_pos);
self.check_bounds(char_indx)?;
self.text_rope.insert(char_indx, new_str);
Ok(())
}
pub fn pop_char(&mut self, caret_pos: Position) {
let char_indx = self.pos_to_char_indx(caret_pos);
if (char_indx > 0) && char_indx <= self.text_rope.len_chars() {
self.text_rope.remove((char_indx - 1)..char_indx);
}
}
pub fn del_selection(&mut self, raw_sel: RawSelection) -> EdResult<()> {
let (start_char_indx, end_char_indx) = self.sel_to_tup(raw_sel)?;
self.check_bounds(end_char_indx)?;
self.text_rope.remove(start_char_indx..end_char_indx);
Ok(())
}
pub fn get_selection(&self, raw_sel: RawSelection) -> EdResult<&str> {
let (start_char_indx, end_char_indx) = self.sel_to_tup(raw_sel)?;
self.check_bounds(end_char_indx)?;
let rope_slice = self.text_rope.slice(start_char_indx..end_char_indx);
if let Some(line_str_ref) = rope_slice.as_str() {
Ok(line_str_ref)
} else {
// happens very rarely
let line_str = rope_slice.chunks().collect::<String>();
let arena_str_ref = self.arena.alloc(line_str);
Ok(arena_str_ref)
}
}
fn check_bounds(&self, char_indx: usize) -> EdResult<()> {
ensure!(
char_indx <= self.text_rope.len_chars(),
OutOfBounds {
index: char_indx,
collection_name: "Rope",
len: self.text_rope.len_chars()
}
);
Ok(())
}
pub fn line(&self, line_nr: usize) -> Option<&str> {
if line_nr < self.nr_of_lines() {
let rope_slice = self.text_rope.line(line_nr);
if let Some(line_str_ref) = rope_slice.as_str() {
Some(line_str_ref)
} else {
// happens very rarely
let line_str = rope_slice.chunks().collect::<String>();
let arena_str_ref = self.arena.alloc(line_str);
Some(arena_str_ref)
}
} else {
None
}
}
pub fn line_len(&self, line_nr: usize) -> Option<usize> {
self.line(line_nr).map(|line| line.len())
}
pub fn line_len_res(&self, line_nr: usize) -> EdResult<usize> {
self.line_len(line_nr).context(OutOfBounds {
index: line_nr,
collection_name: "Rope",
len: self.text_rope.len_lines(),
})
}
pub fn nr_of_lines(&self) -> usize {
self.text_rope.len_lines()
}
pub fn nr_of_chars(&self) -> usize {
self.text_rope.len_chars()
}
// expensive function, don't use it if it can be done with a specialized, more efficient function
// TODO use pool allocation here
pub fn all_lines<'a>(&self, arena: &'a Bump) -> BumpString<'a> {
let mut lines = BumpString::with_capacity_in(self.text_rope.len_chars(), arena);
for line in self.text_rope.lines() {
lines.extend(line.as_str());
}
lines
}
fn pos_to_char_indx(&self, pos: Position) -> usize {
self.text_rope.line_to_char(pos.line) + pos.column
}
fn char_indx_to_pos(&self, char_indx: usize) -> Position {
let line = self.text_rope.char_to_line(char_indx);
let char_idx_line_start = self.pos_to_char_indx(Position { line, column: 0 });
let column = char_indx - char_idx_line_start;
Position { line, column }
}
fn sel_to_tup(&self, raw_sel: RawSelection) -> EdResult<(usize, usize)> {
let valid_sel = validate_selection(raw_sel)?;
let start_char_indx = self.pos_to_char_indx(valid_sel.selection.start_pos);
let end_char_indx = self.pos_to_char_indx(valid_sel.selection.end_pos);
Ok((start_char_indx, end_char_indx))
}
pub fn last_position(&self) -> Position {
self.char_indx_to_pos(self.nr_of_chars())
}
}
pub fn from_path(path: &Path) -> EdResult<TextBuffer> {
let text_rope = rope_from_path(path)?;
let path_str = path_to_string(path);
let arena = Bump::new();
Ok(TextBuffer {
text_rope,
path_str,
arena,
})
}
fn path_to_string(path: &Path) -> String {
let mut path_str = String::new();
path_str.push_str(&path.to_string_lossy());
path_str
}
fn rope_from_path(path: &Path) -> EdResult<Rope> {
match File::open(path) {
Ok(file) => {
let buf_reader = &mut io::BufReader::new(file);
match Rope::from_reader(buf_reader) {
Ok(rope) => Ok(rope),
Err(e) => Err(TextBufReadFailed {
path_str: path_to_string(path),
err_msg: e.to_string(),
}),
}
}
Err(e) => Err(FileOpenFailed {
path_str: path_to_string(path),
err_msg: e.to_string(),
}),
}
}
impl fmt::Debug for TextBuffer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TextBuffer")
.field("text_rope", &self.text_rope)
.field("path_str", &self.path_str)
.finish()
}
}

8
editor/src/ui/colors.rs Normal file
View file

@ -0,0 +1,8 @@
use crate::graphics::colors as gr_colors;
use gr_colors::ColorTup;
pub const LIGHT_BRAND_COL: ColorTup = (0.506, 0.337, 0.902, 1.0);
//pub const DARK_BRAND_COL: ColorTup = (0.380, 0.169, 0.871, 1.0);
pub const TXT_COL: ColorTup = (1.0, 1.0, 1.0, 1.0);
pub const CARET_COL: ColorTup = gr_colors::WHITE;
pub const SELECT_COL: ColorTup = (0.45, 0.61, 1.0, 1.0);

4
editor/src/ui/mod.rs Normal file
View file

@ -0,0 +1,4 @@
pub mod colors;
pub mod text;
pub mod ui_error;
mod util;

View file

@ -0,0 +1,78 @@
use super::selection::validate_selection;
use super::selection::Selection;
use super::text_pos::TextPos;
use crate::ui::ui_error::UIResult;
use winit::event::ModifiersState;
#[derive(Debug, Copy, Clone)]
pub struct CaretWSelect {
pub caret_pos: TextPos,
pub selection_opt: Option<Selection>,
}
fn mk_some_sel(start_pos: TextPos, end_pos: TextPos) -> UIResult<Option<Selection>> {
Ok(Some(validate_selection(start_pos, end_pos)?))
}
impl Default for CaretWSelect {
fn default() -> Self {
Self {
caret_pos: TextPos { line: 0, column: 0 },
selection_opt: None,
}
}
}
impl CaretWSelect {
pub fn new(caret_pos: TextPos, selection_opt: Option<Selection>) -> Self {
Self {
caret_pos,
selection_opt,
}
}
pub fn move_caret_w_mods(&mut self, new_pos: TextPos, mods: &ModifiersState) -> UIResult<()> {
let caret_pos = self.caret_pos;
// one does not simply move the caret
let valid_sel_opt = if new_pos != caret_pos {
if mods.shift() {
if let Some(old_sel) = self.selection_opt {
if new_pos < old_sel.start_pos {
if caret_pos > old_sel.start_pos {
mk_some_sel(new_pos, old_sel.start_pos)?
} else {
mk_some_sel(new_pos, old_sel.end_pos)?
}
} else if new_pos > old_sel.end_pos {
if caret_pos < old_sel.end_pos {
mk_some_sel(old_sel.end_pos, new_pos)?
} else {
mk_some_sel(old_sel.start_pos, new_pos)?
}
} else if new_pos > caret_pos {
mk_some_sel(new_pos, old_sel.end_pos)?
} else if new_pos < caret_pos {
mk_some_sel(old_sel.start_pos, new_pos)?
} else {
// TODO should this return none?
None
}
} else if new_pos < self.caret_pos {
mk_some_sel(new_pos, caret_pos)?
} else {
mk_some_sel(caret_pos, new_pos)?
}
} else {
None
}
} else {
self.selection_opt
};
self.caret_pos = new_pos;
self.selection_opt = valid_sel_opt;
Ok(())
}
}

View file

@ -0,0 +1,70 @@
// Adapted from https://github.com/cessen/ropey by Nathan Vegdahl, licensed under the MIT license
use crate::ui::text::{
selection::{RawSelection, Selection},
text_pos::TextPos,
};
use crate::ui::ui_error::UIResult;
use bumpalo::collections::String as BumpString;
use bumpalo::Bump;
use winit::event::{ModifiersState, VirtualKeyCode};
pub trait Lines {
fn get_line(&self, line_nr: usize) -> UIResult<&str>;
fn line_len(&self, line_nr: usize) -> UIResult<usize>;
fn nr_of_lines(&self) -> usize;
fn nr_of_chars(&self) -> usize;
// TODO use pool allocation here
fn all_lines<'a>(&self, arena: &'a Bump) -> BumpString<'a>;
}
pub trait SelectableLines {
fn get_caret(self) -> TextPos;
fn set_caret(&mut self, caret_pos: TextPos);
fn move_caret_left(&mut self, shift_pressed: bool) -> UIResult<()>;
fn move_caret_right(&mut self, shift_pressed: bool) -> UIResult<()>;
fn move_caret_up(&mut self, shift_pressed: bool) -> UIResult<()>;
fn move_caret_down(&mut self, shift_pressed: bool) -> UIResult<()>;
fn get_selection(&self) -> Option<Selection>;
fn is_selection_active(&self) -> bool;
fn get_selected_str(&self) -> UIResult<Option<&str>>;
fn set_raw_sel(&mut self, raw_sel: RawSelection) -> UIResult<()>;
fn set_sel_none(&mut self);
fn select_all(&mut self) -> UIResult<()>;
fn last_text_pos(&self) -> TextPos;
}
pub trait MutSelectableLines {
fn insert_char(&mut self, new_char: &char) -> UIResult<()>;
// could be for insertion, backspace, del...
fn handle_new_char(&mut self, received_char: &char) -> UIResult<()>;
fn insert_str(&mut self, new_str: &str) -> UIResult<()>;
fn pop_char(&mut self) -> UIResult<()>;
fn del_selection(&mut self) -> UIResult<()>;
fn handle_key_down(
&mut self,
modifiers: &ModifiersState,
virtual_keycode: VirtualKeyCode,
) -> UIResult<()>;
}

View file

@ -0,0 +1,5 @@
pub mod big_selectable_text;
pub mod caret_w_select;
pub mod lines;
pub mod selection;
pub mod text_pos;

View file

@ -0,0 +1,141 @@
use super::lines::Lines;
use super::text_pos::TextPos;
use crate::ui::colors;
use crate::ui::ui_error::{InvalidSelection, UIResult};
use bumpalo::collections::Vec as BumpVec;
use snafu::ensure;
use std::fmt;
#[derive(Debug, Copy, Clone)]
pub struct RawSelection {
pub start_pos: TextPos,
pub end_pos: TextPos,
}
impl std::fmt::Display for RawSelection {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"RawSelection: start_pos: line:{} col:{}, end_pos: line:{} col:{}",
self.start_pos.line, self.start_pos.column, self.end_pos.line, self.end_pos.column
)
}
}
#[derive(Debug, Copy, Clone)]
pub struct Selection {
pub start_pos: TextPos,
pub end_pos: TextPos,
}
pub fn validate_raw_sel(raw_sel: RawSelection) -> UIResult<Selection> {
validate_selection(raw_sel.start_pos, raw_sel.end_pos)
}
pub fn validate_selection(start_pos: TextPos, end_pos: TextPos) -> UIResult<Selection> {
ensure!(
start_pos.line <= end_pos.line,
InvalidSelection {
err_msg: format!(
"start_pos.line ({}) should be smaller than or equal to end_pos.line ({})",
start_pos.line, end_pos.line
)
}
);
ensure!(
!(start_pos.line == end_pos.line && start_pos.column > end_pos.column),
InvalidSelection {
err_msg: format!(
"start_pos.column ({}) should be smaller than or equal to end_pos.column ({}) when start_pos.line equals end_pos.line",
start_pos.column,
end_pos.column
)
}
);
Ok(Selection { start_pos, end_pos })
}
use crate::graphics::primitives::rect::Rect;
use bumpalo::Bump;
pub fn create_selection_rects<'a>(
valid_sel: Selection,
lines: &dyn Lines,
glyph_dim_rect: &Rect,
arena: &'a Bump,
) -> UIResult<BumpVec<'a, Rect>> {
let Selection { start_pos, end_pos } = valid_sel;
let mut all_rects: BumpVec<Rect> = BumpVec::new_in(arena);
let height = glyph_dim_rect.height;
let start_y = glyph_dim_rect.top_left_coords.y + height * (start_pos.line as f32);
let line_start_x = glyph_dim_rect.top_left_coords.x;
if start_pos.line == end_pos.line {
let width = ((end_pos.column as f32) * glyph_dim_rect.width)
- ((start_pos.column as f32) * glyph_dim_rect.width);
let sel_rect_x = line_start_x + ((start_pos.column as f32) * glyph_dim_rect.width);
all_rects.push(Rect {
top_left_coords: (sel_rect_x, start_y).into(),
width,
height,
color: colors::SELECT_COL,
});
Ok(all_rects)
} else {
// first line
let end_col = lines.line_len(start_pos.line)?;
let width = ((end_col as f32) * glyph_dim_rect.width)
- ((start_pos.column as f32) * glyph_dim_rect.width);
let sel_rect_x = line_start_x + ((start_pos.column as f32) * glyph_dim_rect.width);
all_rects.push(Rect {
top_left_coords: (sel_rect_x, start_y).into(),
width,
height,
color: colors::SELECT_COL,
});
//middle lines
let nr_mid_lines = (end_pos.line - start_pos.line) - 1;
let first_mid_line = start_pos.line + 1;
for i in first_mid_line..(first_mid_line + nr_mid_lines) {
let mid_line_len = lines.line_len(i)?;
let width = (mid_line_len as f32) * glyph_dim_rect.width;
let sel_rect_y = start_y + ((i - start_pos.line) as f32) * glyph_dim_rect.height;
all_rects.push(Rect {
top_left_coords: (line_start_x, sel_rect_y).into(),
width,
height,
color: colors::SELECT_COL,
});
}
//last line
if end_pos.column > 0 {
let sel_rect_y =
start_y + ((end_pos.line - start_pos.line) as f32) * glyph_dim_rect.height;
let width = (end_pos.column as f32) * glyph_dim_rect.width;
all_rects.push(Rect {
top_left_coords: (line_start_x, sel_rect_y).into(),
width,
height,
color: colors::SELECT_COL,
});
}
Ok(all_rects)
}
}

View file

@ -0,0 +1,27 @@
use std::cmp::Ordering;
#[derive(Debug, Copy, Clone)]
pub struct TextPos {
pub line: usize,
pub column: usize,
}
impl Ord for TextPos {
fn cmp(&self, other: &Self) -> Ordering {
(self.line, self.column).cmp(&(other.line, other.column))
}
}
impl PartialOrd for TextPos {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for TextPos {
fn eq(&self, other: &Self) -> bool {
(self.line, self.column) == (other.line, other.column)
}
}
impl Eq for TextPos {}

50
editor/src/ui/ui_error.rs Normal file
View file

@ -0,0 +1,50 @@
use snafu::{Backtrace, Snafu};
//import errors as follows:
// `use crate::error::OutOfBounds;`
// *not* `use crate::error::EdError::OutOfBounds;`
// see https://github.com/shepmaster/snafu/issues/211
#[derive(Debug, Snafu)]
#[snafu(visibility(pub))]
pub enum UIError {
#[snafu(display(
"OutOfBounds: index {} was out of bounds for {} with length {}.",
index,
collection_name,
len
))]
OutOfBounds {
index: usize,
collection_name: String,
len: usize,
backtrace: Backtrace,
},
#[snafu(display("InvalidSelection: {}.", err_msg))]
InvalidSelection {
err_msg: String,
backtrace: Backtrace,
},
#[snafu(display(
"FileOpenFailed: failed to open file with path {} with the following error: {}.",
path_str,
err_msg
))]
FileOpenFailed { path_str: String, err_msg: String },
#[snafu(display("TextBufReadFailed: the file {} could be opened but we encountered the following error while trying to read it: {}.", path_str, err_msg))]
TextBufReadFailed { path_str: String, err_msg: String },
#[snafu(display("MissingGlyphDims: glyph_dim_rect_opt was None. It needs to be set using the example_code_glyph_rect function."))]
MissingGlyphDims { backtrace: Backtrace },
}
pub type UIResult<T, E = UIError> = std::result::Result<T, E>;
impl From<UIError> for String {
fn from(ui_error: UIError) -> Self {
format!("{}", ui_error)
}
}

View file

@ -1,40 +0,0 @@
#[macro_use]
extern crate pretty_assertions;
#[macro_use]
extern crate indoc;
#[cfg(test)]
mod test_file {
use bumpalo::Bump;
use roc_editor::lang::roc_file::File;
use std::path::Path;
#[test]
fn read_and_fmt_simple_roc_module() {
let simple_module_path = Path::new("./tests/modules/SimpleUnformatted.roc");
let arena = Bump::new();
let file = File::read(simple_module_path, &arena)
.expect("Could not read SimpleUnformatted.roc in test_file test");
assert_eq!(
file.fmt(),
indoc!(
r#"
interface Simple
exposes [
v, x
]
imports []
v : Str
v = "Value!"
x : Int
x = 4"#
)
);
}
}

View file

@ -1,6 +1,6 @@
interface AStar exposes [ findPath, Model, initialModel ] imports [Quicksort]
interface AStar exposes [ findPath, Model, initialModel, cheapestOpen, takeStep, reconstructPath ] imports [Quicksort]
findPath = \costFn, moveFn, start, end ->
findPath = \costFn, moveFn, start, end ->
astar costFn moveFn end (initialModel start)
Model position :
@ -14,9 +14,9 @@ Model position :
initialModel : position -> Model position
initialModel = \start ->
{
evaluated : Set.empty,
evaluated : Set.empty,
openSet : Set.singleton start,
costs : Dict.singleton start 0,
costs : Dict.singleton start 0,
cameFrom : Dict.empty
}
@ -50,44 +50,28 @@ reconstructPath = \cameFrom, goal ->
updateCost : position, position, Model position -> Model position
updateCost = \current, neighbor, model ->
newCameFrom =
Dict.insert model.cameFrom neighbor current
newCosts =
Dict.insert model.costs neighbor distanceTo
distanceTo =
reconstructPath newCameFrom neighbor
|> List.len
|> Num.toFloat
newModel =
{ model &
costs: newCosts,
cameFrom: newCameFrom
}
when Dict.get model.costs neighbor is
Err _ ->
newCameFrom =
Dict.insert model.cameFrom neighbor current
newCosts =
Dict.insert model.costs neighbor distanceTo
distanceTo =
reconstructPath newCameFrom neighbor
|> List.len
|> Num.toFloat
{ model &
costs: newCosts,
cameFrom: newCameFrom
}
newModel
Ok previousDistance ->
newCameFrom =
Dict.insert model.cameFrom neighbor current
newCosts =
Dict.insert model.costs neighbor distanceTo
distanceTo =
reconstructPath newCameFrom neighbor
|> List.len
|> Num.toFloat
newModel =
{ model &
costs: newCosts,
cameFrom: newCameFrom
}
if distanceTo < previousDistance then
newModel
@ -126,3 +110,27 @@ astar = \costFn, moveFn, goal, model ->
Set.walk newNeighbors (\n, m -> updateCost current n m) modelWithNeighbors
astar costFn moveFn goal modelWithCosts
takeStep = \moveFn, _goal, model, current ->
modelPopped =
{ model &
openSet: Set.remove model.openSet current,
evaluated: Set.insert model.evaluated current,
}
neighbors = moveFn current
newNeighbors = Set.difference neighbors modelPopped.evaluated
modelWithNeighbors = { modelPopped & openSet: Set.union modelPopped.openSet newNeighbors }
# a lot goes wrong here
modelWithCosts =
Set.walk newNeighbors (\n, m -> updateCost current n m) modelWithNeighbors
modelWithCosts

View file

@ -3,20 +3,18 @@ app "astar-tests"
imports [base.Task, AStar]
provides [ main ] to base
fromList : List a -> Set a
fromList = \list -> List.walk list (\x, a -> Set.insert a x) Set.empty
main : Task.Task {} []
main =
Task.after Task.getInt \n ->
when n is
1 ->
Task.putLine (showBool test1)
Task.putLine (showBool test1)
_ ->
ns = Str.fromInt n
Task.putLine "No test \(ns)"
# Task.after Task.getInt \n ->
# when n is
# 1 ->
# Task.putLine (showBool test1)
#
# _ ->
# ns = Str.fromInt n
# Task.putLine "No test \(ns)"
showBool : Bool -> Str
showBool = \b ->
@ -26,17 +24,17 @@ showBool = \b ->
test1 : Bool
test1 =
example1 == [3, 4]
example1 == [2, 4]
example1 : List I64
example1 =
step : I64 -> Set I64
step = \n ->
when n is
1 -> fromList [ 2,3 ]
2 -> fromList [4]
3 -> fromList [4]
_ -> fromList []
1 -> Set.fromList [ 2,3 ]
2 -> Set.fromList [4]
3 -> Set.fromList [4]
_ -> Set.fromList []
cost : I64, I64 -> F64
cost = \_, _ -> 1

View file

@ -0,0 +1,58 @@
app "closure"
packages { base: "platform" }
imports [base.Task]
provides [ main ] to base
# see https://github.com/rtfeldman/roc/issues/985
main : Task.Task {} []
main = closure1 {}
|> Task.after (\_ -> closure2 {})
|> Task.after (\_ -> closure2 {})
|> Task.after (\_ -> closure2 {})
# ---
closure1 : {} -> Task.Task {} []
closure1 = \_ ->
Task.succeed (foo toUnitBorrowed "a long string such that it's malloced")
|> Task.map (\_ -> {})
toUnitBorrowed = \x -> Str.countGraphemes x
foo = \f, x -> f x
# ---
closure2 : {} -> Task.Task {} []
closure2 = \_ ->
x : Str
x = "a long string such that it's malloced"
Task.succeed {}
|> Task.map (\_ -> x)
|> Task.map toUnit
toUnit = (\_ -> {})
# ---
closure3 : {} -> Task.Task {} []
closure3 = \_ ->
x : Str
x = "a long string such that it's malloced"
Task.succeed {}
|> Task.after (\_ -> Task.succeed x |> Task.map (\_ -> {}))
# ---
closure4 : {} -> Task.Task {} []
closure4 = \_ ->
x : Str
x = "a long string such that it's malloced"
Task.succeed {}
|> Task.after (\_ -> Task.succeed x)
|> Task.map (\_ -> {})

View file

@ -1,15 +0,0 @@
app "closure1"
packages { base: "platform" }
imports [base.Task]
provides [ main ] to base
# see https://github.com/rtfeldman/roc/issues/985
main : Task.Task {} []
main =
Task.succeed (foo toUnitBorrowed "a long string such that it's malloced")
|> Task.map (\_ -> {})
toUnitBorrowed = \x -> Str.countGraphemes x
foo = \f, x -> f x

View file

@ -1,15 +0,0 @@
app "closure2"
packages { base: "platform" }
imports [base.Task]
provides [ main ] to base
# see https://github.com/rtfeldman/roc/issues/985
main : Task.Task {} []
main =
x : Str
x = "a long string such that it's malloced"
Task.succeed {}
|> Task.map (\_ -> x)
|> Task.map (\_ -> {})

View file

@ -1,15 +0,0 @@
app "closure3"
packages { base: "platform" }
imports [base.Task]
provides [ main ] to base
# see https://github.com/rtfeldman/roc/issues/985
main : Task.Task {} []
main =
x : Str
x = "a long string such that it's malloced"
Task.succeed {}
|> Task.after (\_ -> Task.succeed x |> Task.map (\_ -> {}))

View file

@ -1,16 +0,0 @@
app "closure4"
packages { base: "platform" }
imports [base.Task]
provides [ main ] to base
# see https://github.com/rtfeldman/roc/issues/985
main : Task.Task {} []
main =
x : Str
x = "a long string such that it's malloced"
Task.succeed {}
|> Task.after (\_ -> Task.succeed x)
|> Task.map (\_ -> {})

View file

@ -0,0 +1,77 @@
interface Quicksort exposes [ sortBy, show ] imports []
show : List I64 -> Str
show = \list ->
if List.isEmpty list then
"[]"
else
content =
list
|> List.map Str.fromInt
|> Str.joinWith ", "
"[ \(content) ]"
sortBy : List a, (a -> Num *) -> List a
sortBy = \list, toComparable ->
sortWith list (\x, y -> Num.compare (toComparable x) (toComparable y))
Order a : a, a -> [ LT, GT, EQ ]
sortWith : List a, (a, a -> [ LT, GT, EQ ]) -> List a
sortWith = \list, order ->
n = List.len list
quicksortHelp list order 0 (n - 1)
quicksortHelp : List a, Order a, Nat, Nat -> List a
quicksortHelp = \list, order, low, high ->
if low < high then
when partition low high list order is
Pair partitionIndex partitioned ->
partitioned
|> quicksortHelp order low (partitionIndex - 1)
|> quicksortHelp order (partitionIndex + 1) high
else
list
partition : Nat, Nat, List a, Order a -> [ Pair Nat (List a) ]
partition = \low, high, initialList, order ->
when List.get initialList high is
Ok pivot ->
when partitionHelp (low - 1) low initialList order high pivot is
Pair newI newList ->
Pair (newI + 1) (swap (newI + 1) high newList)
Err _ ->
Pair (low - 1) initialList
partitionHelp : Nat, Nat, List c, Order c, Nat, c -> [ Pair Nat (List c) ]
partitionHelp = \i, j, list, order, high, pivot ->
if j < high then
when List.get list j is
Ok value ->
when order value pivot is
LT | EQ ->
partitionHelp (i + 1) (j + 1) (swap (i + 1) j list) order high pivot
GT ->
partitionHelp i (j + 1) list order high pivot
Err _ ->
Pair i list
else
Pair i list
swap : Nat, Nat, List a -> List a
swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) ->
list
|> List.set i atJ
|> List.set j atI
_ ->
[]

View file

@ -84,9 +84,8 @@ let
# faster builds - see https://github.com/rtfeldman/roc/blob/trunk/BUILDING_FROM_SOURCE.md#use-lld-for-the-linker
llvmPkgs.lld
# dev tools
rust-analyzer
# rust-analyzer
# (import ./nix/zls.nix { inherit pkgs zig; })
ccls
];
in mkShell (nixos-env // {