mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-30 15:21:12 +00:00
Merge branch 'trunk' into task_can
This commit is contained in:
commit
0c8260d71a
25 changed files with 565 additions and 211 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -2569,6 +2569,7 @@ dependencies = [
|
||||||
"roc_can",
|
"roc_can",
|
||||||
"roc_collections",
|
"roc_collections",
|
||||||
"roc_constrain",
|
"roc_constrain",
|
||||||
|
"roc_fmt",
|
||||||
"roc_gen",
|
"roc_gen",
|
||||||
"roc_load",
|
"roc_load",
|
||||||
"roc_module",
|
"roc_module",
|
||||||
|
|
|
@ -4,7 +4,7 @@ const expectEqual = std.testing.expectEqual;
|
||||||
|
|
||||||
// This whole module is a translation of grapheme breaks from
|
// This whole module is a translation of grapheme breaks from
|
||||||
// the https://github.com/JuliaStrings/utf8proc library.
|
// the https://github.com/JuliaStrings/utf8proc library.
|
||||||
// Thanks so much to those developer!
|
// Thanks so much to those developers!
|
||||||
//
|
//
|
||||||
// The only function this file exposes is `isGraphemeBreak`
|
// The only function this file exposes is `isGraphemeBreak`
|
||||||
//
|
//
|
||||||
|
|
|
@ -21,18 +21,19 @@ comptime {
|
||||||
exportStrFn(str.startsWith, "starts_with");
|
exportStrFn(str.startsWith, "starts_with");
|
||||||
exportStrFn(str.endsWith, "ends_with");
|
exportStrFn(str.endsWith, "ends_with");
|
||||||
exportStrFn(str.strConcat, "concat");
|
exportStrFn(str.strConcat, "concat");
|
||||||
exportStrFn(str.strLen, "len");
|
exportStrFn(str.strNumberOfBytes, "number_of_bytes");
|
||||||
|
exportStrFn(str.strFromInt, "from_int");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Export helpers - Must be run inside a comptime
|
// Export helpers - Must be run inside a comptime
|
||||||
fn exportBuiltinFn(comptime fn_target: anytype, comptime fn_name: []const u8) void {
|
fn exportBuiltinFn(comptime func: anytype, comptime func_name: []const u8) void {
|
||||||
@export(fn_target, .{ .name = "roc_builtins." ++ fn_name, .linkage = .Strong });
|
@export(func, .{ .name = "roc_builtins." ++ func_name, .linkage = .Strong });
|
||||||
}
|
}
|
||||||
fn exportNumFn(comptime fn_target: anytype, comptime fn_name: []const u8) void {
|
fn exportNumFn(comptime func: anytype, comptime func_name: []const u8) void {
|
||||||
exportBuiltinFn(fn_target, "num." ++ fn_name);
|
exportBuiltinFn(func, "num." ++ func_name);
|
||||||
}
|
}
|
||||||
fn exportStrFn(comptime fn_target: anytype, comptime fn_name: []const u8) void {
|
fn exportStrFn(comptime func: anytype, comptime func_name: []const u8) void {
|
||||||
exportBuiltinFn(fn_target, "str." ++ fn_name);
|
exportBuiltinFn(func, "str." ++ func_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run all tests in imported modules
|
// Run all tests in imported modules
|
||||||
|
|
|
@ -21,16 +21,16 @@ const RocStr = extern struct {
|
||||||
// This takes ownership of the pointed-to bytes if they won't fit in a
|
// 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.
|
// small string, and returns a (pointer, len) tuple which points to them.
|
||||||
pub fn init(bytes: [*]const u8, length: usize) RocStr {
|
pub fn init(bytes: [*]const u8, length: usize) RocStr {
|
||||||
const rocStrSize = @sizeOf(RocStr);
|
const roc_str_size = @sizeOf(RocStr);
|
||||||
|
|
||||||
if (length < rocStrSize) {
|
if (length < roc_str_size) {
|
||||||
var ret_small_str = RocStr.empty();
|
var ret_small_str = RocStr.empty();
|
||||||
const target_ptr = @ptrToInt(&ret_small_str);
|
const target_ptr = @ptrToInt(&ret_small_str);
|
||||||
var index: u8 = 0;
|
var index: u8 = 0;
|
||||||
|
|
||||||
// TODO isn't there a way to bulk-zero data in Zig?
|
// TODO isn't there a way to bulk-zero data in Zig?
|
||||||
// Zero out the data, just to be safe
|
// Zero out the data, just to be safe
|
||||||
while (index < rocStrSize) {
|
while (index < roc_str_size) {
|
||||||
var offset_ptr = @intToPtr(*u8, target_ptr + index);
|
var offset_ptr = @intToPtr(*u8, target_ptr + index);
|
||||||
offset_ptr.* = 0;
|
offset_ptr.* = 0;
|
||||||
index += 1;
|
index += 1;
|
||||||
|
@ -45,14 +45,28 @@ const RocStr = extern struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// set the final byte to be the length
|
// set the final byte to be the length
|
||||||
const final_byte_ptr = @intToPtr(*u8, target_ptr + rocStrSize - 1);
|
const final_byte_ptr = @intToPtr(*u8, target_ptr + roc_str_size - 1);
|
||||||
final_byte_ptr.* = @truncate(u8, length) ^ 0b10000000;
|
final_byte_ptr.* = @truncate(u8, length) ^ 0b10000000;
|
||||||
|
|
||||||
return ret_small_str;
|
return ret_small_str;
|
||||||
} else {
|
} else {
|
||||||
var new_bytes: [*]u8 = @ptrCast([*]u8, malloc(length));
|
var result = allocateStr(u64, InPlace.Clone, length);
|
||||||
|
|
||||||
@memcpy(new_bytes, bytes, length);
|
@memcpy(@ptrCast([*]u8, result.str_bytes), bytes, length);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
const roc_str_size = @sizeOf(RocStr);
|
||||||
|
|
||||||
|
if (length < roc_str_size) {
|
||||||
|
return RocStr.empty();
|
||||||
|
} else {
|
||||||
|
var new_bytes: [*]u8 = @ptrCast([*]u8, malloc(length));
|
||||||
|
|
||||||
return RocStr{
|
return RocStr{
|
||||||
.str_bytes = new_bytes,
|
.str_bytes = new_bytes,
|
||||||
|
@ -61,8 +75,8 @@ const RocStr = extern struct {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn drop(self: RocStr) void {
|
pub fn deinit(self: RocStr) void {
|
||||||
if (!self.is_small_str()) {
|
if (!self.isSmallStr()) {
|
||||||
const str_bytes: [*]u8 = self.str_bytes orelse unreachable;
|
const str_bytes: [*]u8 = self.str_bytes orelse unreachable;
|
||||||
|
|
||||||
free(str_bytes);
|
free(str_bytes);
|
||||||
|
@ -88,13 +102,14 @@ const RocStr = extern struct {
|
||||||
|
|
||||||
const self_u8_ptr: [*]const u8 = @ptrCast([*]const u8, &self);
|
const self_u8_ptr: [*]const u8 = @ptrCast([*]const u8, &self);
|
||||||
const other_u8_ptr: [*]const u8 = @ptrCast([*]const u8, &other);
|
const other_u8_ptr: [*]const u8 = @ptrCast([*]const u8, &other);
|
||||||
const self_bytes: [*]const u8 = if (self.is_small_str() or self.is_empty()) self_u8_ptr else self_bytes_ptr orelse unreachable;
|
const self_bytes: [*]const u8 = if (self.isSmallStr() or self.isEmpty()) self_u8_ptr else self_bytes_ptr orelse unreachable;
|
||||||
const other_bytes: [*]const u8 = if (other.is_small_str() or other.is_empty()) other_u8_ptr else other_bytes_ptr orelse unreachable;
|
const other_bytes: [*]const u8 = if (other.isSmallStr() or other.isEmpty()) other_u8_ptr else other_bytes_ptr orelse unreachable;
|
||||||
|
|
||||||
var index: usize = 0;
|
var index: usize = 0;
|
||||||
|
|
||||||
// TODO rewrite this into a for loop
|
// TODO rewrite this into a for loop
|
||||||
while (index < self.len()) {
|
const length = self.len();
|
||||||
|
while (index < length) {
|
||||||
if (self_bytes[index] != other_bytes[index]) {
|
if (self_bytes[index] != other_bytes[index]) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -105,7 +120,7 @@ const RocStr = extern struct {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_small_str(self: RocStr) bool {
|
pub fn isSmallStr(self: RocStr) bool {
|
||||||
return @bitCast(isize, self.str_len) < 0;
|
return @bitCast(isize, self.str_len) < 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,17 +132,17 @@ const RocStr = extern struct {
|
||||||
|
|
||||||
// Since this conditional would be prone to branch misprediction,
|
// Since this conditional would be prone to branch misprediction,
|
||||||
// make sure it will compile to a cmov.
|
// make sure it will compile to a cmov.
|
||||||
return if (self.is_small_str()) small_len else big_len;
|
return if (self.isSmallStr()) small_len else big_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_empty(self: RocStr) bool {
|
pub fn isEmpty(self: RocStr) bool {
|
||||||
return self.len() == 0;
|
return self.len() == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_u8_ptr(self: RocStr) [*]u8 {
|
pub fn asU8ptr(self: RocStr) [*]u8 {
|
||||||
const if_small = &@bitCast([16]u8, self);
|
const if_small = &@bitCast([16]u8, self);
|
||||||
const if_big = @ptrCast([*]u8, self.str_bytes);
|
const if_big = @ptrCast([*]u8, self.str_bytes);
|
||||||
return if (self.is_small_str() or self.is_empty()) if_small else if_big;
|
return if (self.isSmallStr() or self.isEmpty()) if_small else if_big;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Given a pointer to some bytes, write the first (len) bytes of this
|
// Given a pointer to some bytes, write the first (len) bytes of this
|
||||||
|
@ -145,7 +160,7 @@ const RocStr = extern struct {
|
||||||
|
|
||||||
// Since this conditional would be prone to branch misprediction,
|
// Since this conditional would be prone to branch misprediction,
|
||||||
// make sure it will compile to a cmov.
|
// make sure it will compile to a cmov.
|
||||||
const src: [*]u8 = if (self.is_small_str()) small_src else big_src;
|
const src: [*]u8 = if (self.isSmallStr()) small_src else big_src;
|
||||||
|
|
||||||
@memcpy(dest, src, len);
|
@memcpy(dest, src, len);
|
||||||
}
|
}
|
||||||
|
@ -164,8 +179,8 @@ const RocStr = extern struct {
|
||||||
// TODO: fix those tests
|
// TODO: fix those tests
|
||||||
// expect(roc_str1.eq(roc_str2));
|
// expect(roc_str1.eq(roc_str2));
|
||||||
|
|
||||||
roc_str1.drop();
|
roc_str1.deinit();
|
||||||
roc_str2.drop();
|
roc_str2.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
test "RocStr.eq: not equal different length" {
|
test "RocStr.eq: not equal different length" {
|
||||||
|
@ -181,8 +196,8 @@ const RocStr = extern struct {
|
||||||
|
|
||||||
expect(!roc_str1.eq(roc_str2));
|
expect(!roc_str1.eq(roc_str2));
|
||||||
|
|
||||||
roc_str1.drop();
|
roc_str1.deinit();
|
||||||
roc_str2.drop();
|
roc_str2.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
test "RocStr.eq: not equal same length" {
|
test "RocStr.eq: not equal same length" {
|
||||||
|
@ -199,17 +214,39 @@ const RocStr = extern struct {
|
||||||
// TODO: fix those tests
|
// TODO: fix those tests
|
||||||
// expect(!roc_str1.eq(roc_str2));
|
// expect(!roc_str1.eq(roc_str2));
|
||||||
|
|
||||||
roc_str1.drop();
|
roc_str1.deinit();
|
||||||
roc_str2.drop();
|
roc_str2.deinit();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Str.numberOfBytes
|
// Str.numberOfBytes
|
||||||
|
|
||||||
pub fn strLen(string: RocStr) callconv(.C) u64 {
|
pub fn strNumberOfBytes(string: RocStr) callconv(.C) usize {
|
||||||
return string.len();
|
return string.len();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Str.fromInt
|
||||||
|
|
||||||
|
pub fn strFromInt(int: i64) callconv(.C) RocStr {
|
||||||
|
// prepare for having multiple integer types in the future
|
||||||
|
return strFromIntHelp(i64, int);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn strFromIntHelp(comptime T: type, int: T) RocStr {
|
||||||
|
// determine maximum size for this T
|
||||||
|
comptime const size = comptime blk: {
|
||||||
|
// the string representation of the minimum i128 value uses at most 40 characters
|
||||||
|
var buf: [40]u8 = undefined;
|
||||||
|
var result = std.fmt.bufPrint(&buf, "{}", .{std.math.minInt(T)}) catch unreachable;
|
||||||
|
break :blk result.len;
|
||||||
|
};
|
||||||
|
|
||||||
|
var buf: [size]u8 = undefined;
|
||||||
|
const result = std.fmt.bufPrint(&buf, "{}", .{int}) catch unreachable;
|
||||||
|
|
||||||
|
return RocStr.init(&buf, result.len);
|
||||||
|
}
|
||||||
|
|
||||||
// Str.split
|
// Str.split
|
||||||
|
|
||||||
pub fn strSplitInPlace(array: [*]RocStr, array_len: usize, string: RocStr, delimiter: RocStr) callconv(.C) void {
|
pub fn strSplitInPlace(array: [*]RocStr, array_len: usize, string: RocStr, delimiter: RocStr) callconv(.C) void {
|
||||||
|
@ -217,10 +254,10 @@ pub fn strSplitInPlace(array: [*]RocStr, array_len: usize, string: RocStr, delim
|
||||||
var sliceStart_index: usize = 0;
|
var sliceStart_index: usize = 0;
|
||||||
var str_index: usize = 0;
|
var str_index: usize = 0;
|
||||||
|
|
||||||
const str_bytes = string.as_u8_ptr();
|
const str_bytes = string.asU8ptr();
|
||||||
const str_len = string.len();
|
const str_len = string.len();
|
||||||
|
|
||||||
const delimiter_bytes_ptrs = delimiter.as_u8_ptr();
|
const delimiter_bytes_ptrs = delimiter.asU8ptr();
|
||||||
const delimiter_len = delimiter.len();
|
const delimiter_len = delimiter.len();
|
||||||
|
|
||||||
if (str_len > delimiter_len) {
|
if (str_len > delimiter_len) {
|
||||||
|
@ -278,11 +315,11 @@ test "strSplitInPlace: no delimiter" {
|
||||||
expect(array[0].eq(expected[0]));
|
expect(array[0].eq(expected[0]));
|
||||||
|
|
||||||
for (array) |roc_str| {
|
for (array) |roc_str| {
|
||||||
roc_str.drop();
|
roc_str.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (expected) |roc_str| {
|
for (expected) |roc_str| {
|
||||||
roc_str.drop();
|
roc_str.deinit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -378,10 +415,10 @@ test "strSplitInPlace: three pieces" {
|
||||||
// needs to be broken into, so that we can allocate a array
|
// needs to be broken into, so that we can allocate a array
|
||||||
// of that size. It always returns at least 1.
|
// of that size. It always returns at least 1.
|
||||||
pub fn countSegments(string: RocStr, delimiter: RocStr) callconv(.C) usize {
|
pub fn countSegments(string: RocStr, delimiter: RocStr) callconv(.C) usize {
|
||||||
const str_bytes = string.as_u8_ptr();
|
const str_bytes = string.asU8ptr();
|
||||||
const str_len = string.len();
|
const str_len = string.len();
|
||||||
|
|
||||||
const delimiter_bytes_ptrs = delimiter.as_u8_ptr();
|
const delimiter_bytes_ptrs = delimiter.asU8ptr();
|
||||||
const delimiter_len = delimiter.len();
|
const delimiter_len = delimiter.len();
|
||||||
|
|
||||||
var count: usize = 1;
|
var count: usize = 1;
|
||||||
|
@ -464,12 +501,12 @@ test "countSegments: delimiter interspered" {
|
||||||
const grapheme = @import("helpers/grapheme.zig");
|
const grapheme = @import("helpers/grapheme.zig");
|
||||||
|
|
||||||
pub fn countGraphemeClusters(string: RocStr) callconv(.C) usize {
|
pub fn countGraphemeClusters(string: RocStr) callconv(.C) usize {
|
||||||
if (string.is_empty()) {
|
if (string.isEmpty()) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bytes_len = string.len();
|
const bytes_len = string.len();
|
||||||
const bytes_ptr = string.as_u8_ptr();
|
const bytes_ptr = string.asU8ptr();
|
||||||
|
|
||||||
var bytes = bytes_ptr[0..bytes_len];
|
var bytes = bytes_ptr[0..bytes_len];
|
||||||
var iter = (unicode.Utf8View.init(bytes) catch unreachable).iterator();
|
var iter = (unicode.Utf8View.init(bytes) catch unreachable).iterator();
|
||||||
|
@ -498,7 +535,7 @@ pub fn countGraphemeClusters(string: RocStr) callconv(.C) usize {
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn roc_str_from_literal(bytes_arr: *const []u8) RocStr {}
|
fn rocStrFromLiteral(bytes_arr: *const []u8) RocStr {}
|
||||||
|
|
||||||
test "countGraphemeClusters: empty string" {
|
test "countGraphemeClusters: empty string" {
|
||||||
const count = countGraphemeClusters(RocStr.empty());
|
const count = countGraphemeClusters(RocStr.empty());
|
||||||
|
@ -544,10 +581,10 @@ test "countGraphemeClusters: emojis, ut8, and ascii characters" {
|
||||||
|
|
||||||
pub fn startsWith(string: RocStr, prefix: RocStr) callconv(.C) bool {
|
pub fn startsWith(string: RocStr, prefix: RocStr) callconv(.C) bool {
|
||||||
const bytes_len = string.len();
|
const bytes_len = string.len();
|
||||||
const bytes_ptr = string.as_u8_ptr();
|
const bytes_ptr = string.asU8ptr();
|
||||||
|
|
||||||
const prefix_len = prefix.len();
|
const prefix_len = prefix.len();
|
||||||
const prefix_ptr = prefix.as_u8_ptr();
|
const prefix_ptr = prefix.asU8ptr();
|
||||||
|
|
||||||
if (prefix_len > bytes_len) {
|
if (prefix_len > bytes_len) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -586,10 +623,10 @@ test "startsWith: 12345678912345678910 starts with 123456789123456789" {
|
||||||
|
|
||||||
pub fn endsWith(string: RocStr, suffix: RocStr) callconv(.C) bool {
|
pub fn endsWith(string: RocStr, suffix: RocStr) callconv(.C) bool {
|
||||||
const bytes_len = string.len();
|
const bytes_len = string.len();
|
||||||
const bytes_ptr = string.as_u8_ptr();
|
const bytes_ptr = string.asU8ptr();
|
||||||
|
|
||||||
const suffix_len = suffix.len();
|
const suffix_len = suffix.len();
|
||||||
const suffix_ptr = suffix.as_u8_ptr();
|
const suffix_ptr = suffix.asU8ptr();
|
||||||
|
|
||||||
if (suffix_len > bytes_len) {
|
if (suffix_len > bytes_len) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -653,10 +690,10 @@ test "RocStr.concat: small concat small" {
|
||||||
|
|
||||||
expect(roc_str3.eq(result));
|
expect(roc_str3.eq(result));
|
||||||
|
|
||||||
roc_str1.drop();
|
roc_str1.deinit();
|
||||||
roc_str2.drop();
|
roc_str2.deinit();
|
||||||
roc_str3.drop();
|
roc_str3.deinit();
|
||||||
result.drop();
|
result.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn strConcat(ptr_size: u32, result_in_place: InPlace, arg1: RocStr, arg2: RocStr) callconv(.C) RocStr {
|
pub fn strConcat(ptr_size: u32, result_in_place: InPlace, arg1: RocStr, arg2: RocStr) callconv(.C) RocStr {
|
||||||
|
@ -668,10 +705,10 @@ pub fn strConcat(ptr_size: u32, result_in_place: InPlace, arg1: RocStr, arg2: Ro
|
||||||
}
|
}
|
||||||
|
|
||||||
fn strConcatHelp(comptime T: type, result_in_place: InPlace, arg1: RocStr, arg2: RocStr) RocStr {
|
fn strConcatHelp(comptime T: type, result_in_place: InPlace, arg1: RocStr, arg2: RocStr) RocStr {
|
||||||
if (arg1.is_empty()) {
|
if (arg1.isEmpty()) {
|
||||||
return cloneNonemptyStr(T, result_in_place, arg2);
|
return cloneStr(T, result_in_place, arg2);
|
||||||
} else if (arg2.is_empty()) {
|
} else if (arg2.isEmpty()) {
|
||||||
return cloneNonemptyStr(T, result_in_place, arg1);
|
return cloneStr(T, result_in_place, arg1);
|
||||||
} else {
|
} else {
|
||||||
const combined_length = arg1.len() + arg2.len();
|
const combined_length = arg1.len() + arg2.len();
|
||||||
|
|
||||||
|
@ -679,12 +716,12 @@ fn strConcatHelp(comptime T: type, result_in_place: InPlace, arg1: RocStr, arg2:
|
||||||
const result_is_big = combined_length >= small_str_bytes;
|
const result_is_big = combined_length >= small_str_bytes;
|
||||||
|
|
||||||
if (result_is_big) {
|
if (result_is_big) {
|
||||||
var result = allocate_str(T, result_in_place, combined_length);
|
var result = allocateStr(T, result_in_place, combined_length);
|
||||||
|
|
||||||
{
|
{
|
||||||
const old_if_small = &@bitCast([16]u8, arg1);
|
const old_if_small = &@bitCast([16]u8, arg1);
|
||||||
const old_if_big = @ptrCast([*]u8, arg1.str_bytes);
|
const old_if_big = @ptrCast([*]u8, arg1.str_bytes);
|
||||||
const old_bytes = if (arg1.is_small_str()) old_if_small else old_if_big;
|
const old_bytes = if (arg1.isSmallStr()) old_if_small else old_if_big;
|
||||||
|
|
||||||
const new_bytes: [*]u8 = @ptrCast([*]u8, result.str_bytes);
|
const new_bytes: [*]u8 = @ptrCast([*]u8, result.str_bytes);
|
||||||
|
|
||||||
|
@ -694,7 +731,7 @@ fn strConcatHelp(comptime T: type, result_in_place: InPlace, arg1: RocStr, arg2:
|
||||||
{
|
{
|
||||||
const old_if_small = &@bitCast([16]u8, arg2);
|
const old_if_small = &@bitCast([16]u8, arg2);
|
||||||
const old_if_big = @ptrCast([*]u8, arg2.str_bytes);
|
const old_if_big = @ptrCast([*]u8, arg2.str_bytes);
|
||||||
const old_bytes = if (arg2.is_small_str()) old_if_small else old_if_big;
|
const old_bytes = if (arg2.isSmallStr()) old_if_small else old_if_big;
|
||||||
|
|
||||||
const new_bytes = @ptrCast([*]u8, result.str_bytes) + arg1.len();
|
const new_bytes = @ptrCast([*]u8, result.str_bytes) + arg1.len();
|
||||||
|
|
||||||
|
@ -738,12 +775,12 @@ const InPlace = packed enum(u8) {
|
||||||
Clone,
|
Clone,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn cloneNonemptyStr(comptime T: type, in_place: InPlace, str: RocStr) RocStr {
|
fn cloneStr(comptime T: type, in_place: InPlace, str: RocStr) RocStr {
|
||||||
if (str.is_small_str() or str.is_empty()) {
|
if (str.isSmallStr() or str.isEmpty()) {
|
||||||
// just return the bytes
|
// just return the bytes
|
||||||
return str;
|
return str;
|
||||||
} else {
|
} else {
|
||||||
var new_str = allocate_str(T, in_place, str.str_len);
|
var new_str = allocateStr(T, in_place, str.str_len);
|
||||||
|
|
||||||
var old_bytes: [*]u8 = @ptrCast([*]u8, str.str_bytes);
|
var old_bytes: [*]u8 = @ptrCast([*]u8, str.str_bytes);
|
||||||
var new_bytes: [*]u8 = @ptrCast([*]u8, new_str.str_bytes);
|
var new_bytes: [*]u8 = @ptrCast([*]u8, new_str.str_bytes);
|
||||||
|
@ -754,7 +791,7 @@ fn cloneNonemptyStr(comptime T: type, in_place: InPlace, str: RocStr) RocStr {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn allocate_str(comptime T: type, in_place: InPlace, number_of_chars: u64) RocStr {
|
fn allocateStr(comptime T: type, in_place: InPlace, number_of_chars: u64) RocStr {
|
||||||
const length = @sizeOf(T) + number_of_chars;
|
const length = @sizeOf(T) + number_of_chars;
|
||||||
var new_bytes: [*]T = @ptrCast([*]T, @alignCast(@alignOf(T), malloc(length)));
|
var new_bytes: [*]T = @ptrCast([*]T, @alignCast(@alignOf(T), malloc(length)));
|
||||||
|
|
||||||
|
@ -765,7 +802,7 @@ fn allocate_str(comptime T: type, in_place: InPlace, number_of_chars: u64) RocSt
|
||||||
}
|
}
|
||||||
|
|
||||||
var first_element = @ptrCast([*]align(@alignOf(T)) u8, new_bytes);
|
var first_element = @ptrCast([*]align(@alignOf(T)) u8, new_bytes);
|
||||||
first_element += 8;
|
first_element += @sizeOf(usize);
|
||||||
|
|
||||||
return RocStr{
|
return RocStr{
|
||||||
.str_bytes = first_element,
|
.str_bytes = first_element,
|
||||||
|
|
|
@ -29,4 +29,5 @@ pub const STR_STR_SPLIT_IN_PLACE: &str = "roc_builtins.str.str_split_in_place";
|
||||||
pub const STR_COUNT_GRAPEHEME_CLUSTERS: &str = "roc_builtins.str.count_grapheme_clusters";
|
pub const STR_COUNT_GRAPEHEME_CLUSTERS: &str = "roc_builtins.str.count_grapheme_clusters";
|
||||||
pub const STR_STARTS_WITH: &str = "roc_builtins.str.starts_with";
|
pub const STR_STARTS_WITH: &str = "roc_builtins.str.starts_with";
|
||||||
pub const STR_ENDS_WITH: &str = "roc_builtins.str.ends_with";
|
pub const STR_ENDS_WITH: &str = "roc_builtins.str.ends_with";
|
||||||
pub const STR_LEN: &str = "roc_builtins.str.len";
|
pub const STR_NUMBER_OF_BYTES: &str = "roc_builtins.str.number_of_bytes";
|
||||||
|
pub const STR_FROM_INT: &str = "roc_builtins.str.from_int";
|
||||||
|
|
|
@ -429,6 +429,12 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
||||||
top_level_function(vec![str_type()], Box::new(int_type())),
|
top_level_function(vec![str_type()], Box::new(int_type())),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// fromInt : Int -> Str
|
||||||
|
add_type(
|
||||||
|
Symbol::STR_FROM_INT,
|
||||||
|
top_level_function(vec![int_type()], Box::new(str_type())),
|
||||||
|
);
|
||||||
|
|
||||||
// List module
|
// List module
|
||||||
|
|
||||||
// get : List elem, Int -> Result elem [ OutOfBounds ]*
|
// get : List elem, Int -> Result elem [ OutOfBounds ]*
|
||||||
|
|
|
@ -1108,6 +1108,12 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
||||||
unique_function(vec![str_type(star1)], int_type(star2))
|
unique_function(vec![str_type(star1)], int_type(star2))
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// fromInt : Attr * Int -> Attr * Str
|
||||||
|
add_type(Symbol::STR_FROM_INT, {
|
||||||
|
let_tvars! { star1, star2 };
|
||||||
|
unique_function(vec![int_type(star1)], str_type(star2))
|
||||||
|
});
|
||||||
|
|
||||||
// Result module
|
// Result module
|
||||||
|
|
||||||
// map : Attr * (Result (Attr a e))
|
// map : Attr * (Result (Attr a e))
|
||||||
|
|
|
@ -56,6 +56,7 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap<Symbol, Def> {
|
||||||
Symbol::STR_STARTS_WITH => str_starts_with,
|
Symbol::STR_STARTS_WITH => str_starts_with,
|
||||||
Symbol::STR_ENDS_WITH => str_ends_with,
|
Symbol::STR_ENDS_WITH => str_ends_with,
|
||||||
Symbol::STR_COUNT_GRAPHEMES => str_count_graphemes,
|
Symbol::STR_COUNT_GRAPHEMES => str_count_graphemes,
|
||||||
|
Symbol::STR_FROM_INT => str_from_int,
|
||||||
Symbol::LIST_LEN => list_len,
|
Symbol::LIST_LEN => list_len,
|
||||||
Symbol::LIST_GET => list_get,
|
Symbol::LIST_GET => list_get,
|
||||||
Symbol::LIST_SET => list_set,
|
Symbol::LIST_SET => list_set,
|
||||||
|
@ -1030,6 +1031,26 @@ fn str_count_graphemes(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Str.fromInt : Int -> Str
|
||||||
|
fn str_from_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
|
let int_var = var_store.fresh();
|
||||||
|
let str_var = var_store.fresh();
|
||||||
|
|
||||||
|
let body = RunLowLevel {
|
||||||
|
op: LowLevel::StrFromInt,
|
||||||
|
args: vec![(int_var, Var(Symbol::ARG_1))],
|
||||||
|
ret_var: str_var,
|
||||||
|
};
|
||||||
|
|
||||||
|
defn(
|
||||||
|
symbol,
|
||||||
|
vec![(int_var, Symbol::ARG_1)],
|
||||||
|
var_store,
|
||||||
|
body,
|
||||||
|
str_var,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// List.concat : List elem, List elem -> List elem
|
/// List.concat : List elem, List elem -> List elem
|
||||||
fn list_concat(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
fn list_concat(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
let list_var = var_store.fresh();
|
let list_var = var_store.fresh();
|
||||||
|
|
|
@ -4,8 +4,8 @@ use crate::llvm::build_list::{
|
||||||
list_reverse, list_set, list_single, list_sum, list_walk, list_walk_backwards,
|
list_reverse, list_set, list_single, list_sum, list_walk, list_walk_backwards,
|
||||||
};
|
};
|
||||||
use crate::llvm::build_str::{
|
use crate::llvm::build_str::{
|
||||||
str_concat, str_count_graphemes, str_ends_with, str_len, str_split, str_starts_with,
|
str_concat, str_count_graphemes, str_ends_with, str_from_int, str_number_of_bytes, str_split,
|
||||||
CHAR_LAYOUT,
|
str_starts_with, CHAR_LAYOUT,
|
||||||
};
|
};
|
||||||
use crate::llvm::compare::{build_eq, build_neq};
|
use crate::llvm::compare::{build_eq, build_neq};
|
||||||
use crate::llvm::convert::{
|
use crate::llvm::convert::{
|
||||||
|
@ -2427,23 +2427,25 @@ fn run_low_level<'a, 'ctx, 'env>(
|
||||||
|
|
||||||
let inplace = get_inplace_from_layout(layout);
|
let inplace = get_inplace_from_layout(layout);
|
||||||
|
|
||||||
str_concat(env, inplace, scope, parent, args[0], args[1])
|
str_concat(env, inplace, scope, args[0], args[1])
|
||||||
}
|
}
|
||||||
StrStartsWith => {
|
StrStartsWith => {
|
||||||
// Str.startsWith : Str, Str -> Bool
|
// Str.startsWith : Str, Str -> Bool
|
||||||
debug_assert_eq!(args.len(), 2);
|
debug_assert_eq!(args.len(), 2);
|
||||||
|
|
||||||
let inplace = get_inplace_from_layout(layout);
|
str_starts_with(env, scope, args[0], args[1])
|
||||||
|
|
||||||
str_starts_with(env, inplace, scope, parent, args[0], args[1])
|
|
||||||
}
|
}
|
||||||
StrEndsWith => {
|
StrEndsWith => {
|
||||||
// Str.startsWith : Str, Str -> Bool
|
// Str.startsWith : Str, Str -> Bool
|
||||||
debug_assert_eq!(args.len(), 2);
|
debug_assert_eq!(args.len(), 2);
|
||||||
|
|
||||||
let inplace = get_inplace_from_layout(layout);
|
str_ends_with(env, scope, args[0], args[1])
|
||||||
|
}
|
||||||
|
StrFromInt => {
|
||||||
|
// Str.fromInt : Int -> Str
|
||||||
|
debug_assert_eq!(args.len(), 1);
|
||||||
|
|
||||||
str_ends_with(env, inplace, scope, parent, args[0], args[1])
|
str_from_int(env, scope, args[0])
|
||||||
}
|
}
|
||||||
StrSplit => {
|
StrSplit => {
|
||||||
// Str.split : Str, Str -> List Str
|
// Str.split : Str, Str -> List Str
|
||||||
|
@ -2451,13 +2453,13 @@ fn run_low_level<'a, 'ctx, 'env>(
|
||||||
|
|
||||||
let inplace = get_inplace_from_layout(layout);
|
let inplace = get_inplace_from_layout(layout);
|
||||||
|
|
||||||
str_split(env, scope, parent, inplace, args[0], args[1])
|
str_split(env, scope, inplace, args[0], args[1])
|
||||||
}
|
}
|
||||||
StrIsEmpty => {
|
StrIsEmpty => {
|
||||||
// Str.isEmpty : Str -> Str
|
// Str.isEmpty : Str -> Str
|
||||||
debug_assert_eq!(args.len(), 1);
|
debug_assert_eq!(args.len(), 1);
|
||||||
|
|
||||||
let len = str_len(env, scope, args[0]);
|
let len = str_number_of_bytes(env, scope, args[0]);
|
||||||
let is_zero = env.builder.build_int_compare(
|
let is_zero = env.builder.build_int_compare(
|
||||||
IntPredicate::EQ,
|
IntPredicate::EQ,
|
||||||
len,
|
len,
|
||||||
|
@ -2470,7 +2472,7 @@ fn run_low_level<'a, 'ctx, 'env>(
|
||||||
// Str.countGraphemes : Str -> Int
|
// Str.countGraphemes : Str -> Int
|
||||||
debug_assert_eq!(args.len(), 1);
|
debug_assert_eq!(args.len(), 1);
|
||||||
|
|
||||||
str_count_graphemes(env, scope, parent, args[0])
|
str_count_graphemes(env, scope, args[0])
|
||||||
}
|
}
|
||||||
ListLen => {
|
ListLen => {
|
||||||
// List.len : List * -> Int
|
// List.len : List * -> Int
|
||||||
|
|
|
@ -3,7 +3,7 @@ use crate::llvm::build::{
|
||||||
};
|
};
|
||||||
use crate::llvm::compare::build_eq;
|
use crate::llvm::compare::build_eq;
|
||||||
use crate::llvm::convert::{basic_type_from_layout, collection, get_ptr_type};
|
use crate::llvm::convert::{basic_type_from_layout, collection, get_ptr_type};
|
||||||
use crate::llvm::refcounting::decrement_refcount_layout;
|
use crate::llvm::refcounting::{decrement_refcount_layout, increment_refcount_layout};
|
||||||
use inkwell::builder::Builder;
|
use inkwell::builder::Builder;
|
||||||
use inkwell::context::Context;
|
use inkwell::context::Context;
|
||||||
use inkwell::types::{BasicTypeEnum, PointerType};
|
use inkwell::types::{BasicTypeEnum, PointerType};
|
||||||
|
@ -1318,6 +1318,85 @@ pub fn list_keep_if_help<'a, 'ctx, 'env>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// List.map : List before, (before -> after) -> List after
|
||||||
|
macro_rules! list_map_help {
|
||||||
|
($env:expr, $layout_ids:expr, $inplace:expr, $parent:expr, $func:expr, $func_layout:expr, $list:expr, $list_layout:expr, $function_ptr:expr, $function_return_layout: expr, $closure_info:expr) => {{
|
||||||
|
let layout_ids = $layout_ids;
|
||||||
|
let inplace = $inplace;
|
||||||
|
let parent = $parent;
|
||||||
|
let func = $func;
|
||||||
|
let func_layout = $func_layout;
|
||||||
|
let list = $list;
|
||||||
|
let list_layout = $list_layout;
|
||||||
|
let function_ptr = $function_ptr;
|
||||||
|
let function_return_layout = $function_return_layout;
|
||||||
|
let closure_info : Option<(&Layout, BasicValueEnum)> = $closure_info;
|
||||||
|
|
||||||
|
|
||||||
|
let non_empty_fn = |elem_layout: &Layout<'a>,
|
||||||
|
len: IntValue<'ctx>,
|
||||||
|
list_wrapper: StructValue<'ctx>| {
|
||||||
|
let ctx = $env.context;
|
||||||
|
let builder = $env.builder;
|
||||||
|
|
||||||
|
let ret_list_ptr = allocate_list($env, inplace, function_return_layout, len);
|
||||||
|
|
||||||
|
let elem_type = basic_type_from_layout($env.arena, ctx, elem_layout, $env.ptr_bytes);
|
||||||
|
let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic);
|
||||||
|
|
||||||
|
let list_ptr = load_list_ptr(builder, list_wrapper, ptr_type);
|
||||||
|
|
||||||
|
let list_loop = |index, before_elem| {
|
||||||
|
increment_refcount_layout($env, parent, layout_ids, before_elem, elem_layout);
|
||||||
|
|
||||||
|
let arguments = match closure_info {
|
||||||
|
Some((closure_data_layout, closure_data)) => {
|
||||||
|
increment_refcount_layout( $env, parent, layout_ids, closure_data, closure_data_layout);
|
||||||
|
|
||||||
|
bumpalo::vec![in $env.arena; before_elem, closure_data]
|
||||||
|
}
|
||||||
|
None => bumpalo::vec![in $env.arena; before_elem],
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
let call_site_value = builder.build_call(function_ptr, &arguments, "map_func");
|
||||||
|
|
||||||
|
// set the calling convention explicitly for this call
|
||||||
|
call_site_value.set_call_convention(crate::llvm::build::FAST_CALL_CONV);
|
||||||
|
|
||||||
|
let after_elem = call_site_value
|
||||||
|
.try_as_basic_value()
|
||||||
|
.left()
|
||||||
|
.unwrap_or_else(|| panic!("LLVM error: Invalid call by pointer."));
|
||||||
|
|
||||||
|
// The pointer to the element in the mapped-over list
|
||||||
|
let after_elem_ptr = unsafe {
|
||||||
|
builder.build_in_bounds_gep(ret_list_ptr, &[index], "load_index_after_list")
|
||||||
|
};
|
||||||
|
|
||||||
|
// Mutate the new array in-place to change the element.
|
||||||
|
builder.build_store(after_elem_ptr, after_elem);
|
||||||
|
};
|
||||||
|
|
||||||
|
incrementing_elem_loop(builder, ctx, parent, list_ptr, len, "#index", list_loop);
|
||||||
|
|
||||||
|
let result = store_list($env, ret_list_ptr, len);
|
||||||
|
|
||||||
|
// decrement the input list and function (if it's a closure)
|
||||||
|
decrement_refcount_layout($env, parent, layout_ids, list, list_layout);
|
||||||
|
decrement_refcount_layout($env, parent, layout_ids, func, func_layout);
|
||||||
|
|
||||||
|
if let Some((closure_data_layout, closure_data)) = closure_info {
|
||||||
|
decrement_refcount_layout( $env, parent, layout_ids, closure_data, closure_data_layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
};
|
||||||
|
|
||||||
|
if_list_is_not_empty($env, parent, non_empty_fn, list, list_layout, "List.map")
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
/// List.map : List before, (before -> after) -> List after
|
/// List.map : List before, (before -> after) -> List after
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn list_map<'a, 'ctx, 'env>(
|
pub fn list_map<'a, 'ctx, 'env>(
|
||||||
|
@ -1332,56 +1411,24 @@ pub fn list_map<'a, 'ctx, 'env>(
|
||||||
) -> BasicValueEnum<'ctx> {
|
) -> BasicValueEnum<'ctx> {
|
||||||
match (func, func_layout) {
|
match (func, func_layout) {
|
||||||
(BasicValueEnum::PointerValue(func_ptr), Layout::FunctionPointer(_, ret_elem_layout)) => {
|
(BasicValueEnum::PointerValue(func_ptr), Layout::FunctionPointer(_, ret_elem_layout)) => {
|
||||||
let non_empty_fn = |elem_layout: &Layout<'a>,
|
list_map_help!(
|
||||||
len: IntValue<'ctx>,
|
env,
|
||||||
list_wrapper: StructValue<'ctx>| {
|
layout_ids,
|
||||||
let ctx = env.context;
|
inplace,
|
||||||
let builder = env.builder;
|
parent,
|
||||||
|
func,
|
||||||
let ret_list_ptr = allocate_list(env, inplace, ret_elem_layout, len);
|
func_layout,
|
||||||
|
list,
|
||||||
let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes);
|
list_layout,
|
||||||
let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic);
|
func_ptr,
|
||||||
|
ret_elem_layout,
|
||||||
let list_ptr = load_list_ptr(builder, list_wrapper, ptr_type);
|
None
|
||||||
|
)
|
||||||
let list_loop = |index, before_elem| {
|
|
||||||
let call_site_value =
|
|
||||||
builder.build_call(func_ptr, env.arena.alloc([before_elem]), "map_func");
|
|
||||||
|
|
||||||
// set the calling convention explicitly for this call
|
|
||||||
call_site_value.set_call_convention(crate::llvm::build::FAST_CALL_CONV);
|
|
||||||
|
|
||||||
let after_elem = call_site_value
|
|
||||||
.try_as_basic_value()
|
|
||||||
.left()
|
|
||||||
.unwrap_or_else(|| panic!("LLVM error: Invalid call by pointer."));
|
|
||||||
|
|
||||||
// The pointer to the element in the mapped-over list
|
|
||||||
let after_elem_ptr = unsafe {
|
|
||||||
builder.build_in_bounds_gep(ret_list_ptr, &[index], "load_index_after_list")
|
|
||||||
};
|
|
||||||
|
|
||||||
// Mutate the new array in-place to change the element.
|
|
||||||
builder.build_store(after_elem_ptr, after_elem);
|
|
||||||
};
|
|
||||||
|
|
||||||
incrementing_elem_loop(builder, ctx, parent, list_ptr, len, "#index", list_loop);
|
|
||||||
|
|
||||||
let result = store_list(env, ret_list_ptr, len);
|
|
||||||
|
|
||||||
decrement_refcount_layout(env, parent, layout_ids, list, list_layout);
|
|
||||||
|
|
||||||
result
|
|
||||||
};
|
|
||||||
|
|
||||||
if_list_is_not_empty(env, parent, non_empty_fn, list, list_layout, "List.map")
|
|
||||||
}
|
}
|
||||||
(BasicValueEnum::StructValue(ptr_and_data), Layout::Closure(_, _, ret_elem_layout)) => {
|
(
|
||||||
let non_empty_fn = |elem_layout: &Layout<'a>,
|
BasicValueEnum::StructValue(ptr_and_data),
|
||||||
len: IntValue<'ctx>,
|
Layout::Closure(_, closure_layout, ret_elem_layout),
|
||||||
list_wrapper: StructValue<'ctx>| {
|
) => {
|
||||||
let ctx = env.context;
|
|
||||||
let builder = env.builder;
|
let builder = env.builder;
|
||||||
|
|
||||||
let func_ptr = builder
|
let func_ptr = builder
|
||||||
|
@ -1393,43 +1440,21 @@ pub fn list_map<'a, 'ctx, 'env>(
|
||||||
.build_extract_value(ptr_and_data, 1, "closure_data")
|
.build_extract_value(ptr_and_data, 1, "closure_data")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let ret_list_ptr = allocate_list(env, inplace, ret_elem_layout, len);
|
let closure_data_layout = closure_layout.as_block_of_memory_layout();
|
||||||
|
|
||||||
let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes);
|
list_map_help!(
|
||||||
let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic);
|
env,
|
||||||
|
layout_ids,
|
||||||
let list_ptr = load_list_ptr(builder, list_wrapper, ptr_type);
|
inplace,
|
||||||
|
parent,
|
||||||
let list_loop = |index, before_elem| {
|
func,
|
||||||
let call_site_value = builder.build_call(
|
func_layout,
|
||||||
|
list,
|
||||||
|
list_layout,
|
||||||
func_ptr,
|
func_ptr,
|
||||||
env.arena.alloc([before_elem, closure_data]),
|
ret_elem_layout,
|
||||||
"map_func",
|
Some((&closure_data_layout, closure_data))
|
||||||
);
|
)
|
||||||
|
|
||||||
// set the calling convention explicitly for this call
|
|
||||||
call_site_value.set_call_convention(crate::llvm::build::FAST_CALL_CONV);
|
|
||||||
|
|
||||||
let after_elem = call_site_value
|
|
||||||
.try_as_basic_value()
|
|
||||||
.left()
|
|
||||||
.unwrap_or_else(|| panic!("LLVM error: Invalid call by pointer."));
|
|
||||||
|
|
||||||
// The pointer to the element in the mapped-over list
|
|
||||||
let after_elem_ptr = unsafe {
|
|
||||||
builder.build_in_bounds_gep(ret_list_ptr, &[index], "load_index_after_list")
|
|
||||||
};
|
|
||||||
|
|
||||||
// Mutate the new array in-place to change the element.
|
|
||||||
builder.build_store(after_elem_ptr, after_elem);
|
|
||||||
};
|
|
||||||
|
|
||||||
incrementing_elem_loop(builder, ctx, parent, list_ptr, len, "#index", list_loop);
|
|
||||||
|
|
||||||
store_list(env, ret_list_ptr, len)
|
|
||||||
};
|
|
||||||
|
|
||||||
if_list_is_not_empty(env, parent, non_empty_fn, list, list_layout, "List.map")
|
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
unreachable!(
|
unreachable!(
|
||||||
|
|
|
@ -4,19 +4,20 @@ use crate::llvm::build::{
|
||||||
use crate::llvm::build_list::{allocate_list, store_list};
|
use crate::llvm::build_list::{allocate_list, store_list};
|
||||||
use crate::llvm::convert::collection;
|
use crate::llvm::convert::collection;
|
||||||
use inkwell::types::BasicTypeEnum;
|
use inkwell::types::BasicTypeEnum;
|
||||||
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, StructValue};
|
use inkwell::values::{BasicValueEnum, IntValue, StructValue};
|
||||||
use inkwell::AddressSpace;
|
use inkwell::AddressSpace;
|
||||||
use roc_builtins::bitcode;
|
use roc_builtins::bitcode;
|
||||||
use roc_module::symbol::Symbol;
|
use roc_module::symbol::Symbol;
|
||||||
use roc_mono::layout::{Builtin, Layout};
|
use roc_mono::layout::{Builtin, Layout};
|
||||||
|
|
||||||
|
use super::build::load_symbol;
|
||||||
|
|
||||||
pub static CHAR_LAYOUT: Layout = Layout::Builtin(Builtin::Int8);
|
pub static CHAR_LAYOUT: Layout = Layout::Builtin(Builtin::Int8);
|
||||||
|
|
||||||
/// Str.split : Str, Str -> List Str
|
/// Str.split : Str, Str -> List Str
|
||||||
pub fn str_split<'a, 'ctx, 'env>(
|
pub fn str_split<'a, 'ctx, 'env>(
|
||||||
env: &Env<'a, 'ctx, 'env>,
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
scope: &Scope<'a, 'ctx>,
|
scope: &Scope<'a, 'ctx>,
|
||||||
_parent: FunctionValue<'ctx>,
|
|
||||||
inplace: InPlace,
|
inplace: InPlace,
|
||||||
str_symbol: Symbol,
|
str_symbol: Symbol,
|
||||||
delimiter_symbol: Symbol,
|
delimiter_symbol: Symbol,
|
||||||
|
@ -60,29 +61,6 @@ pub fn str_split<'a, 'ctx, 'env>(
|
||||||
store_list(env, ret_list_ptr, segment_count)
|
store_list(env, ret_list_ptr, segment_count)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
fn cast_to_zig_str(
|
|
||||||
env: &Env<'a, 'ctx, 'env>,
|
|
||||||
str_as_struct: StructValue<'ctx>,
|
|
||||||
) -> BasicValueEnum<'ctx> {
|
|
||||||
// get the RocStr type defined by zig
|
|
||||||
let roc_str_type = env.module.get_struct_type("str.RocStr").unwrap();
|
|
||||||
|
|
||||||
// convert `{ *mut u8, i64 }` to `RocStr`
|
|
||||||
builder.build_bitcast(str_as_struct, roc_str_type, "convert_to_zig_rocstr");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cast_from_zig_str(
|
|
||||||
env: &Env<'a, 'ctx, 'env>,
|
|
||||||
str_as_struct: StructValue<'ctx>,
|
|
||||||
) -> BasicValueEnum<'ctx> {
|
|
||||||
let ret_type = BasicTypeEnum::StructType(collection(ctx, env.ptr_bytes));
|
|
||||||
|
|
||||||
// convert `RocStr` to `{ *mut u8, i64 }`
|
|
||||||
builder.build_bitcast(str_as_struct, ret_type, "convert_from_zig_rocstr");
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
fn str_symbol_to_i128<'a, 'ctx, 'env>(
|
fn str_symbol_to_i128<'a, 'ctx, 'env>(
|
||||||
env: &Env<'a, 'ctx, 'env>,
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
scope: &Scope<'a, 'ctx>,
|
scope: &Scope<'a, 'ctx>,
|
||||||
|
@ -144,7 +122,6 @@ pub fn str_concat<'a, 'ctx, 'env>(
|
||||||
env: &Env<'a, 'ctx, 'env>,
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
inplace: InPlace,
|
inplace: InPlace,
|
||||||
scope: &Scope<'a, 'ctx>,
|
scope: &Scope<'a, 'ctx>,
|
||||||
_parent: FunctionValue<'ctx>,
|
|
||||||
str1_symbol: Symbol,
|
str1_symbol: Symbol,
|
||||||
str2_symbol: Symbol,
|
str2_symbol: Symbol,
|
||||||
) -> BasicValueEnum<'ctx> {
|
) -> BasicValueEnum<'ctx> {
|
||||||
|
@ -173,7 +150,7 @@ pub fn str_concat<'a, 'ctx, 'env>(
|
||||||
zig_str_to_struct(env, zig_result).into()
|
zig_str_to_struct(env, zig_result).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn str_len<'a, 'ctx, 'env>(
|
pub fn str_number_of_bytes<'a, 'ctx, 'env>(
|
||||||
env: &Env<'a, 'ctx, 'env>,
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
scope: &Scope<'a, 'ctx>,
|
scope: &Scope<'a, 'ctx>,
|
||||||
str_symbol: Symbol,
|
str_symbol: Symbol,
|
||||||
|
@ -181,7 +158,8 @@ pub fn str_len<'a, 'ctx, 'env>(
|
||||||
let str_i128 = str_symbol_to_i128(env, scope, str_symbol);
|
let str_i128 = str_symbol_to_i128(env, scope, str_symbol);
|
||||||
|
|
||||||
// the builtin will always return an u64
|
// the builtin will always return an u64
|
||||||
let length = call_bitcode_fn(env, &[str_i128.into()], &bitcode::STR_LEN).into_int_value();
|
let length =
|
||||||
|
call_bitcode_fn(env, &[str_i128.into()], &bitcode::STR_NUMBER_OF_BYTES).into_int_value();
|
||||||
|
|
||||||
// cast to the appropriate usize of the current build
|
// cast to the appropriate usize of the current build
|
||||||
env.builder
|
env.builder
|
||||||
|
@ -191,9 +169,7 @@ pub fn str_len<'a, 'ctx, 'env>(
|
||||||
/// Str.startsWith : Str, Str -> Bool
|
/// Str.startsWith : Str, Str -> Bool
|
||||||
pub fn str_starts_with<'a, 'ctx, 'env>(
|
pub fn str_starts_with<'a, 'ctx, 'env>(
|
||||||
env: &Env<'a, 'ctx, 'env>,
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
_inplace: InPlace,
|
|
||||||
scope: &Scope<'a, 'ctx>,
|
scope: &Scope<'a, 'ctx>,
|
||||||
_parent: FunctionValue<'ctx>,
|
|
||||||
str_symbol: Symbol,
|
str_symbol: Symbol,
|
||||||
prefix_symbol: Symbol,
|
prefix_symbol: Symbol,
|
||||||
) -> BasicValueEnum<'ctx> {
|
) -> BasicValueEnum<'ctx> {
|
||||||
|
@ -210,9 +186,7 @@ pub fn str_starts_with<'a, 'ctx, 'env>(
|
||||||
/// Str.endsWith : Str, Str -> Bool
|
/// Str.endsWith : Str, Str -> Bool
|
||||||
pub fn str_ends_with<'a, 'ctx, 'env>(
|
pub fn str_ends_with<'a, 'ctx, 'env>(
|
||||||
env: &Env<'a, 'ctx, 'env>,
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
_inplace: InPlace,
|
|
||||||
scope: &Scope<'a, 'ctx>,
|
scope: &Scope<'a, 'ctx>,
|
||||||
_parent: FunctionValue<'ctx>,
|
|
||||||
str_symbol: Symbol,
|
str_symbol: Symbol,
|
||||||
prefix_symbol: Symbol,
|
prefix_symbol: Symbol,
|
||||||
) -> BasicValueEnum<'ctx> {
|
) -> BasicValueEnum<'ctx> {
|
||||||
|
@ -230,7 +204,6 @@ pub fn str_ends_with<'a, 'ctx, 'env>(
|
||||||
pub fn str_count_graphemes<'a, 'ctx, 'env>(
|
pub fn str_count_graphemes<'a, 'ctx, 'env>(
|
||||||
env: &Env<'a, 'ctx, 'env>,
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
scope: &Scope<'a, 'ctx>,
|
scope: &Scope<'a, 'ctx>,
|
||||||
_parent: FunctionValue<'ctx>,
|
|
||||||
str_symbol: Symbol,
|
str_symbol: Symbol,
|
||||||
) -> BasicValueEnum<'ctx> {
|
) -> BasicValueEnum<'ctx> {
|
||||||
let str_i128 = str_symbol_to_i128(env, scope, str_symbol);
|
let str_i128 = str_symbol_to_i128(env, scope, str_symbol);
|
||||||
|
@ -241,3 +214,16 @@ pub fn str_count_graphemes<'a, 'ctx, 'env>(
|
||||||
&bitcode::STR_COUNT_GRAPEHEME_CLUSTERS,
|
&bitcode::STR_COUNT_GRAPEHEME_CLUSTERS,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Str.fromInt : Int -> Str
|
||||||
|
pub fn str_from_int<'a, 'ctx, 'env>(
|
||||||
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
|
scope: &Scope<'a, 'ctx>,
|
||||||
|
int_symbol: Symbol,
|
||||||
|
) -> BasicValueEnum<'ctx> {
|
||||||
|
let int = load_symbol(env, scope, &int_symbol);
|
||||||
|
|
||||||
|
let zig_result = call_bitcode_fn(env, &[int], &bitcode::STR_FROM_INT).into_struct_value();
|
||||||
|
|
||||||
|
zig_str_to_struct(env, zig_result).into()
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use crate::llvm::build::{
|
||||||
cast_basic_basic, cast_struct_struct, create_entry_block_alloca, set_name, Env, Scope,
|
cast_basic_basic, cast_struct_struct, create_entry_block_alloca, set_name, Env, Scope,
|
||||||
FAST_CALL_CONV, LLVM_SADD_WITH_OVERFLOW_I64,
|
FAST_CALL_CONV, LLVM_SADD_WITH_OVERFLOW_I64,
|
||||||
};
|
};
|
||||||
use crate::llvm::build_list::list_len;
|
use crate::llvm::build_list::{incrementing_elem_loop, list_len, load_list};
|
||||||
use crate::llvm::convert::{basic_type_from_layout, block_of_memory, ptr_int};
|
use crate::llvm::convert::{basic_type_from_layout, block_of_memory, ptr_int};
|
||||||
use bumpalo::collections::Vec;
|
use bumpalo::collections::Vec;
|
||||||
use inkwell::context::Context;
|
use inkwell::context::Context;
|
||||||
|
@ -367,7 +367,6 @@ fn decrement_refcount_builtin<'a, 'ctx, 'env>(
|
||||||
List(memory_mode, element_layout) => {
|
List(memory_mode, element_layout) => {
|
||||||
let wrapper_struct = value.into_struct_value();
|
let wrapper_struct = value.into_struct_value();
|
||||||
if element_layout.contains_refcounted() {
|
if element_layout.contains_refcounted() {
|
||||||
use crate::llvm::build_list::{incrementing_elem_loop, load_list};
|
|
||||||
use inkwell::types::BasicType;
|
use inkwell::types::BasicType;
|
||||||
|
|
||||||
let ptr_type =
|
let ptr_type =
|
||||||
|
@ -451,7 +450,6 @@ fn increment_refcount_builtin<'a, 'ctx, 'env>(
|
||||||
List(memory_mode, element_layout) => {
|
List(memory_mode, element_layout) => {
|
||||||
let wrapper_struct = value.into_struct_value();
|
let wrapper_struct = value.into_struct_value();
|
||||||
if element_layout.contains_refcounted() {
|
if element_layout.contains_refcounted() {
|
||||||
use crate::llvm::build_list::{incrementing_elem_loop, load_list};
|
|
||||||
use inkwell::types::BasicType;
|
use inkwell::types::BasicType;
|
||||||
|
|
||||||
let ptr_type =
|
let ptr_type =
|
||||||
|
|
|
@ -490,4 +490,29 @@ mod gen_str {
|
||||||
fn str_starts_with_false_small_str() {
|
fn str_starts_with_false_small_str() {
|
||||||
assert_evals_to!(r#"Str.startsWith "1234" "23""#, false, bool);
|
assert_evals_to!(r#"Str.startsWith "1234" "23""#, false, bool);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn str_from_int() {
|
||||||
|
assert_evals_to!(
|
||||||
|
r#"Str.fromInt 1234"#,
|
||||||
|
roc_std::RocStr::from_slice("1234".as_bytes()),
|
||||||
|
roc_std::RocStr
|
||||||
|
);
|
||||||
|
assert_evals_to!(
|
||||||
|
r#"Str.fromInt 0"#,
|
||||||
|
roc_std::RocStr::from_slice("0".as_bytes()),
|
||||||
|
roc_std::RocStr
|
||||||
|
);
|
||||||
|
assert_evals_to!(
|
||||||
|
r#"Str.fromInt -1"#,
|
||||||
|
roc_std::RocStr::from_slice("-1".as_bytes()),
|
||||||
|
roc_std::RocStr
|
||||||
|
);
|
||||||
|
|
||||||
|
let max = format!("{}", i64::MAX);
|
||||||
|
assert_evals_to!(r#"Str.fromInt Num.maxInt"#, &max, &'static str);
|
||||||
|
|
||||||
|
let min = format!("{}", i64::MIN);
|
||||||
|
assert_evals_to!(r#"Str.fromInt Num.minInt"#, &min, &'static str);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ pub enum LowLevel {
|
||||||
StrEndsWith,
|
StrEndsWith,
|
||||||
StrSplit,
|
StrSplit,
|
||||||
StrCountGraphemes,
|
StrCountGraphemes,
|
||||||
|
StrFromInt,
|
||||||
ListLen,
|
ListLen,
|
||||||
ListGetUnsafe,
|
ListGetUnsafe,
|
||||||
ListSet,
|
ListSet,
|
||||||
|
|
|
@ -749,6 +749,7 @@ define_builtins! {
|
||||||
6 STR_COUNT_GRAPHEMES: "countGraphemes"
|
6 STR_COUNT_GRAPHEMES: "countGraphemes"
|
||||||
7 STR_STARTS_WITH: "startsWith"
|
7 STR_STARTS_WITH: "startsWith"
|
||||||
8 STR_ENDS_WITH: "endsWith"
|
8 STR_ENDS_WITH: "endsWith"
|
||||||
|
9 STR_FROM_INT: "fromInt"
|
||||||
}
|
}
|
||||||
4 LIST: "List" => {
|
4 LIST: "List" => {
|
||||||
0 LIST_LIST: "List" imported // the List.List type alias
|
0 LIST_LIST: "List" imported // the List.List type alias
|
||||||
|
|
|
@ -548,5 +548,6 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
|
||||||
arena.alloc_slice_copy(&[irrelevant])
|
arena.alloc_slice_copy(&[irrelevant])
|
||||||
}
|
}
|
||||||
StrStartsWith | StrEndsWith => arena.alloc_slice_copy(&[owned, borrowed]),
|
StrStartsWith | StrEndsWith => arena.alloc_slice_copy(&[owned, borrowed]),
|
||||||
|
StrFromInt => arena.alloc_slice_copy(&[irrelevant]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -460,7 +460,7 @@ impl<'a> Layout<'a> {
|
||||||
|
|
||||||
pub fn is_refcounted(&self) -> bool {
|
pub fn is_refcounted(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Layout::Builtin(Builtin::List(_, _)) => true,
|
Layout::Builtin(Builtin::List(MemoryMode::Refcounted, _)) => true,
|
||||||
Layout::Builtin(Builtin::Str) => true,
|
Layout::Builtin(Builtin::Str) => true,
|
||||||
Layout::RecursiveUnion(_) => true,
|
Layout::RecursiveUnion(_) => true,
|
||||||
Layout::RecursivePointer => true,
|
Layout::RecursivePointer => true,
|
||||||
|
@ -477,12 +477,12 @@ impl<'a> Layout<'a> {
|
||||||
match self {
|
match self {
|
||||||
Builtin(builtin) => builtin.is_refcounted(),
|
Builtin(builtin) => builtin.is_refcounted(),
|
||||||
PhantomEmptyStruct => false,
|
PhantomEmptyStruct => false,
|
||||||
Struct(fields) => fields.iter().any(|f| f.is_refcounted()),
|
Struct(fields) => fields.iter().any(|f| f.contains_refcounted()),
|
||||||
Union(fields) => fields
|
Union(fields) => fields
|
||||||
.iter()
|
.iter()
|
||||||
.map(|ls| ls.iter())
|
.map(|ls| ls.iter())
|
||||||
.flatten()
|
.flatten()
|
||||||
.any(|f| f.is_refcounted()),
|
.any(|f| f.contains_refcounted()),
|
||||||
RecursiveUnion(_) => true,
|
RecursiveUnion(_) => true,
|
||||||
Closure(_, closure_layout, _) => closure_layout.contains_refcounted(),
|
Closure(_, closure_layout, _) => closure_layout.contains_refcounted(),
|
||||||
FunctionPointer(_, _) | RecursivePointer | Pointer(_) => false,
|
FunctionPointer(_, _) | RecursivePointer | Pointer(_) => false,
|
||||||
|
|
|
@ -1851,7 +1851,6 @@ mod test_mono {
|
||||||
let Test.2 = S Test.9 Test.8;
|
let Test.2 = S Test.9 Test.8;
|
||||||
let Test.5 = 1i64;
|
let Test.5 = 1i64;
|
||||||
let Test.6 = Index 0 Test.2;
|
let Test.6 = Index 0 Test.2;
|
||||||
dec Test.2;
|
|
||||||
let Test.7 = lowlevel Eq Test.5 Test.6;
|
let Test.7 = lowlevel Eq Test.5 Test.6;
|
||||||
if Test.7 then
|
if Test.7 then
|
||||||
let Test.3 = 0i64;
|
let Test.3 = 0i64;
|
||||||
|
@ -1903,15 +1902,12 @@ mod test_mono {
|
||||||
let Test.10 = lowlevel Eq Test.8 Test.9;
|
let Test.10 = lowlevel Eq Test.8 Test.9;
|
||||||
if Test.10 then
|
if Test.10 then
|
||||||
let Test.4 = Index 1 Test.2;
|
let Test.4 = Index 1 Test.2;
|
||||||
dec Test.2;
|
|
||||||
let Test.3 = 1i64;
|
let Test.3 = 1i64;
|
||||||
ret Test.3;
|
ret Test.3;
|
||||||
else
|
else
|
||||||
dec Test.2;
|
|
||||||
let Test.5 = 0i64;
|
let Test.5 = 0i64;
|
||||||
ret Test.5;
|
ret Test.5;
|
||||||
else
|
else
|
||||||
dec Test.2;
|
|
||||||
let Test.6 = 0i64;
|
let Test.6 = 0i64;
|
||||||
ret Test.6;
|
ret Test.6;
|
||||||
"#
|
"#
|
||||||
|
|
|
@ -23,6 +23,8 @@ roc_solve = { path = "../compiler/solve" }
|
||||||
roc_mono = { path = "../compiler/mono" }
|
roc_mono = { path = "../compiler/mono" }
|
||||||
roc_load = { path = "../compiler/load" }
|
roc_load = { path = "../compiler/load" }
|
||||||
roc_gen = { path = "../compiler/gen" }
|
roc_gen = { path = "../compiler/gen" }
|
||||||
|
roc_fmt = { path = "../compiler/fmt" }
|
||||||
|
|
||||||
roc_reporting = { path = "../compiler/reporting" }
|
roc_reporting = { path = "../compiler/reporting" }
|
||||||
# TODO switch to clap 3.0.0 once it's out. Tried adding clap = "~3.0.0-beta.1" and cargo wouldn't accept it
|
# TODO switch to clap 3.0.0 once it's out. Tried adding clap = "~3.0.0-beta.1" and cargo wouldn't accept it
|
||||||
im = "14" # im and im-rc should always have the same version!
|
im = "14" # im and im-rc should always have the same version!
|
||||||
|
|
97
editor/src/file.rs
Normal file
97
editor/src/file.rs
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
use bumpalo::collections::Vec;
|
||||||
|
use bumpalo::Bump;
|
||||||
|
use roc_fmt::def::fmt_def;
|
||||||
|
use roc_fmt::module::fmt_module;
|
||||||
|
use roc_parse::ast::{Attempting, Def, Module};
|
||||||
|
use roc_parse::module::module_defs;
|
||||||
|
use roc_parse::parser;
|
||||||
|
use roc_parse::parser::Parser;
|
||||||
|
use roc_region::all::Located;
|
||||||
|
use std::ffi::OsStr;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::{fs, io};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct File<'a> {
|
||||||
|
path: &'a Path,
|
||||||
|
module_header: Module<'a>,
|
||||||
|
content: Vec<'a, Located<Def<'a>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ReadError {
|
||||||
|
Read(std::io::Error),
|
||||||
|
ParseDefs(parser::Fail),
|
||||||
|
ParseHeader(parser::Fail),
|
||||||
|
DoesntHaveRocExtension,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> File<'a> {
|
||||||
|
pub fn read(path: &'a Path, arena: &'a Bump) -> Result<File<'a>, ReadError> {
|
||||||
|
if path.extension() != Some(OsStr::new("roc")) {
|
||||||
|
return Err(ReadError::DoesntHaveRocExtension);
|
||||||
|
}
|
||||||
|
|
||||||
|
let bytes = fs::read(path).map_err(ReadError::Read)?;
|
||||||
|
|
||||||
|
let allocation = arena.alloc(bytes);
|
||||||
|
|
||||||
|
let module_parse_state = parser::State::new(allocation, Attempting::Module);
|
||||||
|
let parsed_module = roc_parse::module::header().parse(&arena, module_parse_state);
|
||||||
|
|
||||||
|
match parsed_module {
|
||||||
|
Ok((module, state)) => {
|
||||||
|
let parsed_defs = module_defs().parse(&arena, state);
|
||||||
|
|
||||||
|
match parsed_defs {
|
||||||
|
Ok((defs, _)) => Ok(File {
|
||||||
|
path,
|
||||||
|
module_header: module,
|
||||||
|
content: defs,
|
||||||
|
}),
|
||||||
|
Err((error, _)) => Err(ReadError::ParseDefs(error)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err((error, _)) => Err(ReadError::ParseHeader(error)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fmt(&self) -> String {
|
||||||
|
let arena = Bump::new();
|
||||||
|
let mut formatted_file = String::new();
|
||||||
|
|
||||||
|
let mut module_header_buf = bumpalo::collections::String::new_in(&arena);
|
||||||
|
fmt_module(&mut module_header_buf, &self.module_header);
|
||||||
|
|
||||||
|
formatted_file.push_str(module_header_buf.as_str());
|
||||||
|
|
||||||
|
for def in &self.content {
|
||||||
|
let mut def_buf = bumpalo::collections::String::new_in(&arena);
|
||||||
|
|
||||||
|
fmt_def(&mut def_buf, &def.value, 0);
|
||||||
|
|
||||||
|
formatted_file.push_str(def_buf.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
formatted_file
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fmt_then_write_to(&self, write_path: &'a Path) -> io::Result<()> {
|
||||||
|
let formatted_file = self.fmt();
|
||||||
|
|
||||||
|
fs::write(write_path, formatted_file)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fmt_then_write_with_name(&self, new_name: &str) -> io::Result<()> {
|
||||||
|
self.fmt_then_write_to(
|
||||||
|
self.path
|
||||||
|
.with_file_name(new_name)
|
||||||
|
.with_extension("roc")
|
||||||
|
.as_path(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fmt_then_write(&self) -> io::Result<()> {
|
||||||
|
self.fmt_then_write_to(self.path)
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,6 +22,7 @@ use winit::event::{ElementState, ModifiersState, VirtualKeyCode};
|
||||||
use winit::event_loop::ControlFlow;
|
use winit::event_loop::ControlFlow;
|
||||||
|
|
||||||
pub mod ast;
|
pub mod ast;
|
||||||
|
pub mod file;
|
||||||
mod rect;
|
mod rect;
|
||||||
pub mod text_state;
|
pub mod text_state;
|
||||||
mod vertex;
|
mod vertex;
|
||||||
|
|
26
editor/tests/modules/Simple.roc
Normal file
26
editor/tests/modules/Simple.roc
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
interface Simple
|
||||||
|
exposes [
|
||||||
|
v, x
|
||||||
|
]
|
||||||
|
imports []
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
v : Str
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
v = "Value!"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
x : Int
|
||||||
|
x = 4
|
68
editor/tests/modules/Storage.roc
Normal file
68
editor/tests/modules/Storage.roc
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
interface Storage
|
||||||
|
exposes [
|
||||||
|
Storage,
|
||||||
|
decoder,
|
||||||
|
get,
|
||||||
|
listener,
|
||||||
|
set
|
||||||
|
]
|
||||||
|
imports [
|
||||||
|
Map.{ Map },
|
||||||
|
Json.Decode.{ Decoder } as Decode
|
||||||
|
Json.Encode as Encode
|
||||||
|
Ports.FromJs as FromJs
|
||||||
|
Ports.ToJs as ToJs
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
## TYPES ##
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
|
||||||
|
Storage : [
|
||||||
|
@Storage (Map Str Decode.Value)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
## API ##
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
|
||||||
|
get : Storage, Str, Decoder a -> [ Ok a, NotInStorage, DecodeError Decode.Error ]*
|
||||||
|
get = \key, decoder, @Storage map ->
|
||||||
|
when Map.get map key is
|
||||||
|
Ok json ->
|
||||||
|
Decode.decodeValue decoder json
|
||||||
|
|
||||||
|
Err NotFound ->
|
||||||
|
NotInStorage
|
||||||
|
|
||||||
|
|
||||||
|
set : Encode.Value, Str -> Effect {}
|
||||||
|
set json str =
|
||||||
|
ToJs.type "setStorage"
|
||||||
|
|> ToJs.setFields [
|
||||||
|
Field "key" (Encode.str str),
|
||||||
|
Field "value" json
|
||||||
|
]
|
||||||
|
|> ToJs.send
|
||||||
|
|
||||||
|
|
||||||
|
decoder : Decoder Storage
|
||||||
|
decoder =
|
||||||
|
Decode.mapType Decode.value
|
||||||
|
|> Decode.map \map -> @Storage map
|
||||||
|
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
## PORTS INCOMING ##
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
|
||||||
|
listener : (Storage -> msg) -> FromJs.Listener msg
|
||||||
|
listener toMsg =
|
||||||
|
FromJs.listen "storageUpdated"
|
||||||
|
(Decode.map decoder toMsg)
|
||||||
|
|
40
editor/tests/test_file.rs
Normal file
40
editor/tests/test_file.rs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
#[macro_use]
|
||||||
|
extern crate pretty_assertions;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate indoc;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test_file {
|
||||||
|
use bumpalo::Bump;
|
||||||
|
use roc_editor::file::File;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_and_fmt_simple_roc_module() {
|
||||||
|
let simple_module_path = Path::new("./tests/modules/Simple.roc");
|
||||||
|
|
||||||
|
let arena = Bump::new();
|
||||||
|
|
||||||
|
let file = File::read(simple_module_path, &arena)
|
||||||
|
.expect("Could not read Simple.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"#
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,4 +5,16 @@ app "effect-example"
|
||||||
|
|
||||||
main : Task {}
|
main : Task {}
|
||||||
main =
|
main =
|
||||||
Task.putLine "Hello world"
|
when if 1 == 1 then True 3 else False 3.14 is
|
||||||
|
True n -> Effect.putLine (Str.fromInt n)
|
||||||
|
_ -> Effect.putLine "Yay"
|
||||||
|
|
||||||
|
# main : Effect.Effect {} as Fx
|
||||||
|
# main =
|
||||||
|
# if RBTree.isEmpty (RBTree.insert 1 2 Empty) then
|
||||||
|
# Effect.putLine "Yay"
|
||||||
|
# |> Effect.after (\{} -> Effect.getLine)
|
||||||
|
# |> Effect.after (\line -> Effect.putLine line)
|
||||||
|
# else
|
||||||
|
# Effect.putLine "Nay"
|
||||||
|
#
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue