mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-30 23:31:12 +00:00
Merge remote-tracking branch 'origin/trunk' into cli-run-non-app
This commit is contained in:
commit
da089af4b9
84 changed files with 5676 additions and 2877 deletions
|
@ -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
|
||||
|
|
35
Earthfile
35
Earthfile
|
@ -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
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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});
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -3,4 +3,3 @@
|
|||
#![allow(clippy::large_enum_variant)]
|
||||
pub mod bitcode;
|
||||
pub mod std;
|
||||
pub mod unique;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -11,6 +11,7 @@ pub enum LowLevel {
|
|||
StrSplit,
|
||||
StrCountGraphemes,
|
||||
StrFromInt,
|
||||
StrFromUtf8,
|
||||
StrFromFloat,
|
||||
ListLen,
|
||||
ListGetUnsafe,
|
||||
|
|
|
@ -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" => {
|
||||
|
|
|
@ -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]),
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(®ion, &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(®ion, &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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)),
|
||||
},
|
||||
|
|
|
@ -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!
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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"
|
||||
|
|
8
editor/src/editor/colors.rs
Normal file
8
editor/src/editor/colors.rs
Normal 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;
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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
448
editor/src/editor/main.rs
Normal 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
8
editor/src/editor/mod.rs
Normal 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;
|
|
@ -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;
|
||||
|
|
@ -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)?;
|
19
editor/src/editor/mvc/ed_model.rs
Normal file
19
editor/src/editor/mvc/ed_model.rs
Normal 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,
|
||||
})
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
pub mod app_model;
|
||||
pub mod app_update;
|
||||
pub mod ed_model;
|
||||
pub mod ed_update;
|
||||
pub mod ed_view;
|
258
editor/src/editor/render_ast.rs
Normal file
258
editor/src/editor/render_ast.rs
Normal 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),
|
||||
};
|
||||
}
|
|
@ -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 {
|
|
@ -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",
|
|
@ -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 {
|
||||
|
|
|
@ -4,7 +4,6 @@ use cgmath::Vector2;
|
|||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Vertex {
|
||||
#[allow(dead_code)]
|
||||
pub position: Vector2<f32>,
|
||||
pub color: [f32; 4],
|
||||
}
|
||||
|
|
|
@ -2,4 +2,3 @@ pub mod colors;
|
|||
pub mod lowlevel;
|
||||
pub mod primitives;
|
||||
pub mod style;
|
||||
mod syntax_highlight;
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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"#
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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(())
|
||||
}
|
||||
}
|
|
@ -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),
|
||||
};
|
||||
}
|
|
@ -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
8
editor/src/ui/colors.rs
Normal 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
4
editor/src/ui/mod.rs
Normal file
|
@ -0,0 +1,4 @@
|
|||
pub mod colors;
|
||||
pub mod text;
|
||||
pub mod ui_error;
|
||||
mod util;
|
File diff suppressed because it is too large
Load diff
78
editor/src/ui/text/caret_w_select.rs
Normal file
78
editor/src/ui/text/caret_w_select.rs
Normal 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(())
|
||||
}
|
||||
}
|
70
editor/src/ui/text/lines.rs
Normal file
70
editor/src/ui/text/lines.rs
Normal 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<()>;
|
||||
}
|
5
editor/src/ui/text/mod.rs
Normal file
5
editor/src/ui/text/mod.rs
Normal 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;
|
141
editor/src/ui/text/selection.rs
Normal file
141
editor/src/ui/text/selection.rs
Normal 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)
|
||||
}
|
||||
}
|
27
editor/src/ui/text/text_pos.rs
Normal file
27
editor/src/ui/text/text_pos.rs
Normal 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
50
editor/src/ui/ui_error.rs
Normal 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)
|
||||
}
|
||||
}
|
|
@ -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"#
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
58
examples/benchmarks/Closure.roc
Normal file
58
examples/benchmarks/Closure.roc
Normal 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 (\_ -> {})
|
||||
|
|
@ -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
|
|
@ -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 (\_ -> {})
|
|
@ -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 (\_ -> {}))
|
||||
|
|
@ -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 (\_ -> {})
|
||||
|
77
examples/benchmarks/Quicksort.roc
Normal file
77
examples/benchmarks/Quicksort.roc
Normal 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
|
||||
|
||||
_ ->
|
||||
[]
|
||||
|
|
@ -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 // {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue