mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-29 14:54:47 +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_collections",
|
||||
"roc_constrain",
|
||||
"roc_fmt",
|
||||
"roc_gen",
|
||||
"roc_load",
|
||||
"roc_module",
|
||||
|
|
|
@ -4,7 +4,7 @@ const expectEqual = std.testing.expectEqual;
|
|||
|
||||
// This whole module is a translation of grapheme breaks from
|
||||
// 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`
|
||||
//
|
||||
|
|
|
@ -21,18 +21,19 @@ comptime {
|
|||
exportStrFn(str.startsWith, "starts_with");
|
||||
exportStrFn(str.endsWith, "ends_with");
|
||||
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
|
||||
fn exportBuiltinFn(comptime fn_target: anytype, comptime fn_name: []const u8) void {
|
||||
@export(fn_target, .{ .name = "roc_builtins." ++ fn_name, .linkage = .Strong });
|
||||
fn exportBuiltinFn(comptime func: anytype, comptime func_name: []const u8) void {
|
||||
@export(func, .{ .name = "roc_builtins." ++ func_name, .linkage = .Strong });
|
||||
}
|
||||
fn exportNumFn(comptime fn_target: anytype, comptime fn_name: []const u8) void {
|
||||
exportBuiltinFn(fn_target, "num." ++ fn_name);
|
||||
fn exportNumFn(comptime func: anytype, comptime func_name: []const u8) void {
|
||||
exportBuiltinFn(func, "num." ++ func_name);
|
||||
}
|
||||
fn exportStrFn(comptime fn_target: anytype, comptime fn_name: []const u8) void {
|
||||
exportBuiltinFn(fn_target, "str." ++ fn_name);
|
||||
fn exportStrFn(comptime func: anytype, comptime func_name: []const u8) void {
|
||||
exportBuiltinFn(func, "str." ++ func_name);
|
||||
}
|
||||
|
||||
// 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
|
||||
// small string, and returns a (pointer, len) tuple which points to them.
|
||||
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();
|
||||
const target_ptr = @ptrToInt(&ret_small_str);
|
||||
var index: u8 = 0;
|
||||
|
||||
// TODO isn't there a way to bulk-zero data in Zig?
|
||||
// Zero out the data, just to be safe
|
||||
while (index < rocStrSize) {
|
||||
while (index < roc_str_size) {
|
||||
var offset_ptr = @intToPtr(*u8, target_ptr + index);
|
||||
offset_ptr.* = 0;
|
||||
index += 1;
|
||||
|
@ -45,14 +45,28 @@ const RocStr = extern struct {
|
|||
}
|
||||
|
||||
// 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;
|
||||
|
||||
return ret_small_str;
|
||||
} 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{
|
||||
.str_bytes = new_bytes,
|
||||
|
@ -61,8 +75,8 @@ const RocStr = extern struct {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn drop(self: RocStr) void {
|
||||
if (!self.is_small_str()) {
|
||||
pub fn deinit(self: RocStr) void {
|
||||
if (!self.isSmallStr()) {
|
||||
const str_bytes: [*]u8 = self.str_bytes orelse unreachable;
|
||||
|
||||
free(str_bytes);
|
||||
|
@ -88,13 +102,14 @@ const RocStr = extern struct {
|
|||
|
||||
const self_u8_ptr: [*]const u8 = @ptrCast([*]const u8, &self);
|
||||
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 other_bytes: [*]const u8 = if (other.is_small_str() or other.is_empty()) other_u8_ptr else other_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.isSmallStr() or other.isEmpty()) other_u8_ptr else other_bytes_ptr orelse unreachable;
|
||||
|
||||
var index: usize = 0;
|
||||
|
||||
// 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]) {
|
||||
return false;
|
||||
}
|
||||
|
@ -105,7 +120,7 @@ const RocStr = extern struct {
|
|||
return true;
|
||||
}
|
||||
|
||||
pub fn is_small_str(self: RocStr) bool {
|
||||
pub fn isSmallStr(self: RocStr) bool {
|
||||
return @bitCast(isize, self.str_len) < 0;
|
||||
}
|
||||
|
||||
|
@ -117,17 +132,17 @@ const RocStr = extern struct {
|
|||
|
||||
// Since this conditional would be prone to branch misprediction,
|
||||
// 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;
|
||||
}
|
||||
|
||||
pub fn as_u8_ptr(self: RocStr) [*]u8 {
|
||||
pub fn asU8ptr(self: RocStr) [*]u8 {
|
||||
const if_small = &@bitCast([16]u8, self);
|
||||
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
|
||||
|
@ -145,7 +160,7 @@ const RocStr = extern struct {
|
|||
|
||||
// Since this conditional would be prone to branch misprediction,
|
||||
// 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);
|
||||
}
|
||||
|
@ -164,8 +179,8 @@ const RocStr = extern struct {
|
|||
// TODO: fix those tests
|
||||
// expect(roc_str1.eq(roc_str2));
|
||||
|
||||
roc_str1.drop();
|
||||
roc_str2.drop();
|
||||
roc_str1.deinit();
|
||||
roc_str2.deinit();
|
||||
}
|
||||
|
||||
test "RocStr.eq: not equal different length" {
|
||||
|
@ -181,8 +196,8 @@ const RocStr = extern struct {
|
|||
|
||||
expect(!roc_str1.eq(roc_str2));
|
||||
|
||||
roc_str1.drop();
|
||||
roc_str2.drop();
|
||||
roc_str1.deinit();
|
||||
roc_str2.deinit();
|
||||
}
|
||||
|
||||
test "RocStr.eq: not equal same length" {
|
||||
|
@ -199,17 +214,39 @@ const RocStr = extern struct {
|
|||
// TODO: fix those tests
|
||||
// expect(!roc_str1.eq(roc_str2));
|
||||
|
||||
roc_str1.drop();
|
||||
roc_str2.drop();
|
||||
roc_str1.deinit();
|
||||
roc_str2.deinit();
|
||||
}
|
||||
};
|
||||
|
||||
// Str.numberOfBytes
|
||||
|
||||
pub fn strLen(string: RocStr) callconv(.C) u64 {
|
||||
pub fn strNumberOfBytes(string: RocStr) callconv(.C) usize {
|
||||
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
|
||||
|
||||
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 str_index: usize = 0;
|
||||
|
||||
const str_bytes = string.as_u8_ptr();
|
||||
const str_bytes = string.asU8ptr();
|
||||
const str_len = string.len();
|
||||
|
||||
const delimiter_bytes_ptrs = delimiter.as_u8_ptr();
|
||||
const delimiter_bytes_ptrs = delimiter.asU8ptr();
|
||||
const delimiter_len = delimiter.len();
|
||||
|
||||
if (str_len > delimiter_len) {
|
||||
|
@ -278,11 +315,11 @@ test "strSplitInPlace: no delimiter" {
|
|||
expect(array[0].eq(expected[0]));
|
||||
|
||||
for (array) |roc_str| {
|
||||
roc_str.drop();
|
||||
roc_str.deinit();
|
||||
}
|
||||
|
||||
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
|
||||
// of that size. It always returns at least 1.
|
||||
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 delimiter_bytes_ptrs = delimiter.as_u8_ptr();
|
||||
const delimiter_bytes_ptrs = delimiter.asU8ptr();
|
||||
const delimiter_len = delimiter.len();
|
||||
|
||||
var count: usize = 1;
|
||||
|
@ -464,12 +501,12 @@ test "countSegments: delimiter interspered" {
|
|||
const grapheme = @import("helpers/grapheme.zig");
|
||||
|
||||
pub fn countGraphemeClusters(string: RocStr) callconv(.C) usize {
|
||||
if (string.is_empty()) {
|
||||
if (string.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
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 iter = (unicode.Utf8View.init(bytes) catch unreachable).iterator();
|
||||
|
@ -498,7 +535,7 @@ pub fn countGraphemeClusters(string: RocStr) callconv(.C) usize {
|
|||
return count;
|
||||
}
|
||||
|
||||
fn roc_str_from_literal(bytes_arr: *const []u8) RocStr {}
|
||||
fn rocStrFromLiteral(bytes_arr: *const []u8) RocStr {}
|
||||
|
||||
test "countGraphemeClusters: empty string" {
|
||||
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 {
|
||||
const bytes_len = string.len();
|
||||
const bytes_ptr = string.as_u8_ptr();
|
||||
const bytes_ptr = string.asU8ptr();
|
||||
|
||||
const prefix_len = prefix.len();
|
||||
const prefix_ptr = prefix.as_u8_ptr();
|
||||
const prefix_ptr = prefix.asU8ptr();
|
||||
|
||||
if (prefix_len > bytes_len) {
|
||||
return false;
|
||||
|
@ -586,10 +623,10 @@ test "startsWith: 12345678912345678910 starts with 123456789123456789" {
|
|||
|
||||
pub fn endsWith(string: RocStr, suffix: RocStr) callconv(.C) bool {
|
||||
const bytes_len = string.len();
|
||||
const bytes_ptr = string.as_u8_ptr();
|
||||
const bytes_ptr = string.asU8ptr();
|
||||
|
||||
const suffix_len = suffix.len();
|
||||
const suffix_ptr = suffix.as_u8_ptr();
|
||||
const suffix_ptr = suffix.asU8ptr();
|
||||
|
||||
if (suffix_len > bytes_len) {
|
||||
return false;
|
||||
|
@ -653,10 +690,10 @@ test "RocStr.concat: small concat small" {
|
|||
|
||||
expect(roc_str3.eq(result));
|
||||
|
||||
roc_str1.drop();
|
||||
roc_str2.drop();
|
||||
roc_str3.drop();
|
||||
result.drop();
|
||||
roc_str1.deinit();
|
||||
roc_str2.deinit();
|
||||
roc_str3.deinit();
|
||||
result.deinit();
|
||||
}
|
||||
|
||||
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 {
|
||||
if (arg1.is_empty()) {
|
||||
return cloneNonemptyStr(T, result_in_place, arg2);
|
||||
} else if (arg2.is_empty()) {
|
||||
return cloneNonemptyStr(T, result_in_place, arg1);
|
||||
if (arg1.isEmpty()) {
|
||||
return cloneStr(T, result_in_place, arg2);
|
||||
} else if (arg2.isEmpty()) {
|
||||
return cloneStr(T, result_in_place, arg1);
|
||||
} else {
|
||||
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;
|
||||
|
||||
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_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);
|
||||
|
||||
|
@ -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_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();
|
||||
|
||||
|
@ -738,12 +775,12 @@ const InPlace = packed enum(u8) {
|
|||
Clone,
|
||||
};
|
||||
|
||||
fn cloneNonemptyStr(comptime T: type, in_place: InPlace, str: RocStr) RocStr {
|
||||
if (str.is_small_str() or str.is_empty()) {
|
||||
fn cloneStr(comptime T: type, in_place: InPlace, str: RocStr) RocStr {
|
||||
if (str.isSmallStr() or str.isEmpty()) {
|
||||
// just return the bytes
|
||||
return str;
|
||||
} 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 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;
|
||||
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);
|
||||
first_element += 8;
|
||||
first_element += @sizeOf(usize);
|
||||
|
||||
return RocStr{
|
||||
.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_STARTS_WITH: &str = "roc_builtins.str.starts_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())),
|
||||
);
|
||||
|
||||
// fromInt : Int -> Str
|
||||
add_type(
|
||||
Symbol::STR_FROM_INT,
|
||||
top_level_function(vec![int_type()], Box::new(str_type())),
|
||||
);
|
||||
|
||||
// List module
|
||||
|
||||
// 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))
|
||||
});
|
||||
|
||||
// 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
|
||||
|
||||
// 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_ENDS_WITH => str_ends_with,
|
||||
Symbol::STR_COUNT_GRAPHEMES => str_count_graphemes,
|
||||
Symbol::STR_FROM_INT => str_from_int,
|
||||
Symbol::LIST_LEN => list_len,
|
||||
Symbol::LIST_GET => list_get,
|
||||
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
|
||||
fn list_concat(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
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,
|
||||
};
|
||||
use crate::llvm::build_str::{
|
||||
str_concat, str_count_graphemes, str_ends_with, str_len, str_split, str_starts_with,
|
||||
CHAR_LAYOUT,
|
||||
str_concat, str_count_graphemes, str_ends_with, str_from_int, str_number_of_bytes, str_split,
|
||||
str_starts_with, CHAR_LAYOUT,
|
||||
};
|
||||
use crate::llvm::compare::{build_eq, build_neq};
|
||||
use crate::llvm::convert::{
|
||||
|
@ -2427,23 +2427,25 @@ fn run_low_level<'a, 'ctx, 'env>(
|
|||
|
||||
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 => {
|
||||
// Str.startsWith : Str, Str -> Bool
|
||||
debug_assert_eq!(args.len(), 2);
|
||||
|
||||
let inplace = get_inplace_from_layout(layout);
|
||||
|
||||
str_starts_with(env, inplace, scope, parent, args[0], args[1])
|
||||
str_starts_with(env, scope, args[0], args[1])
|
||||
}
|
||||
StrEndsWith => {
|
||||
// Str.startsWith : Str, Str -> Bool
|
||||
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 => {
|
||||
// Str.split : Str, Str -> List Str
|
||||
|
@ -2451,13 +2453,13 @@ fn run_low_level<'a, 'ctx, 'env>(
|
|||
|
||||
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 => {
|
||||
// Str.isEmpty : Str -> Str
|
||||
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(
|
||||
IntPredicate::EQ,
|
||||
len,
|
||||
|
@ -2470,7 +2472,7 @@ fn run_low_level<'a, 'ctx, 'env>(
|
|||
// Str.countGraphemes : Str -> Int
|
||||
debug_assert_eq!(args.len(), 1);
|
||||
|
||||
str_count_graphemes(env, scope, parent, args[0])
|
||||
str_count_graphemes(env, scope, args[0])
|
||||
}
|
||||
ListLen => {
|
||||
// List.len : List * -> Int
|
||||
|
|
|
@ -3,7 +3,7 @@ use crate::llvm::build::{
|
|||
};
|
||||
use crate::llvm::compare::build_eq;
|
||||
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::context::Context;
|
||||
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
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn list_map<'a, 'ctx, 'env>(
|
||||
|
@ -1332,56 +1411,24 @@ pub fn list_map<'a, 'ctx, 'env>(
|
|||
) -> BasicValueEnum<'ctx> {
|
||||
match (func, func_layout) {
|
||||
(BasicValueEnum::PointerValue(func_ptr), Layout::FunctionPointer(_, ret_elem_layout)) => {
|
||||
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, ret_elem_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| {
|
||||
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")
|
||||
list_map_help!(
|
||||
env,
|
||||
layout_ids,
|
||||
inplace,
|
||||
parent,
|
||||
func,
|
||||
func_layout,
|
||||
list,
|
||||
list_layout,
|
||||
func_ptr,
|
||||
ret_elem_layout,
|
||||
None
|
||||
)
|
||||
}
|
||||
(BasicValueEnum::StructValue(ptr_and_data), Layout::Closure(_, _, ret_elem_layout)) => {
|
||||
let non_empty_fn = |elem_layout: &Layout<'a>,
|
||||
len: IntValue<'ctx>,
|
||||
list_wrapper: StructValue<'ctx>| {
|
||||
let ctx = env.context;
|
||||
(
|
||||
BasicValueEnum::StructValue(ptr_and_data),
|
||||
Layout::Closure(_, closure_layout, ret_elem_layout),
|
||||
) => {
|
||||
let builder = env.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")
|
||||
.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);
|
||||
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| {
|
||||
let call_site_value = builder.build_call(
|
||||
list_map_help!(
|
||||
env,
|
||||
layout_ids,
|
||||
inplace,
|
||||
parent,
|
||||
func,
|
||||
func_layout,
|
||||
list,
|
||||
list_layout,
|
||||
func_ptr,
|
||||
env.arena.alloc([before_elem, closure_data]),
|
||||
"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);
|
||||
|
||||
store_list(env, ret_list_ptr, len)
|
||||
};
|
||||
|
||||
if_list_is_not_empty(env, parent, non_empty_fn, list, list_layout, "List.map")
|
||||
ret_elem_layout,
|
||||
Some((&closure_data_layout, closure_data))
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
unreachable!(
|
||||
|
|
|
@ -4,19 +4,20 @@ use crate::llvm::build::{
|
|||
use crate::llvm::build_list::{allocate_list, store_list};
|
||||
use crate::llvm::convert::collection;
|
||||
use inkwell::types::BasicTypeEnum;
|
||||
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, StructValue};
|
||||
use inkwell::values::{BasicValueEnum, IntValue, StructValue};
|
||||
use inkwell::AddressSpace;
|
||||
use roc_builtins::bitcode;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_mono::layout::{Builtin, Layout};
|
||||
|
||||
use super::build::load_symbol;
|
||||
|
||||
pub static CHAR_LAYOUT: Layout = Layout::Builtin(Builtin::Int8);
|
||||
|
||||
/// Str.split : Str, Str -> List Str
|
||||
pub fn str_split<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
scope: &Scope<'a, 'ctx>,
|
||||
_parent: FunctionValue<'ctx>,
|
||||
inplace: InPlace,
|
||||
str_symbol: Symbol,
|
||||
delimiter_symbol: Symbol,
|
||||
|
@ -60,29 +61,6 @@ pub fn str_split<'a, 'ctx, 'env>(
|
|||
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>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
scope: &Scope<'a, 'ctx>,
|
||||
|
@ -144,7 +122,6 @@ pub fn str_concat<'a, 'ctx, 'env>(
|
|||
env: &Env<'a, 'ctx, 'env>,
|
||||
inplace: InPlace,
|
||||
scope: &Scope<'a, 'ctx>,
|
||||
_parent: FunctionValue<'ctx>,
|
||||
str1_symbol: Symbol,
|
||||
str2_symbol: Symbol,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
|
@ -173,7 +150,7 @@ pub fn str_concat<'a, 'ctx, 'env>(
|
|||
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>,
|
||||
scope: &Scope<'a, 'ctx>,
|
||||
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);
|
||||
|
||||
// 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
|
||||
env.builder
|
||||
|
@ -191,9 +169,7 @@ pub fn str_len<'a, 'ctx, 'env>(
|
|||
/// Str.startsWith : Str, Str -> Bool
|
||||
pub fn str_starts_with<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
_inplace: InPlace,
|
||||
scope: &Scope<'a, 'ctx>,
|
||||
_parent: FunctionValue<'ctx>,
|
||||
str_symbol: Symbol,
|
||||
prefix_symbol: Symbol,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
|
@ -210,9 +186,7 @@ pub fn str_starts_with<'a, 'ctx, 'env>(
|
|||
/// Str.endsWith : Str, Str -> Bool
|
||||
pub fn str_ends_with<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
_inplace: InPlace,
|
||||
scope: &Scope<'a, 'ctx>,
|
||||
_parent: FunctionValue<'ctx>,
|
||||
str_symbol: Symbol,
|
||||
prefix_symbol: Symbol,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
|
@ -230,7 +204,6 @@ pub fn str_ends_with<'a, 'ctx, 'env>(
|
|||
pub fn str_count_graphemes<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
scope: &Scope<'a, 'ctx>,
|
||||
_parent: FunctionValue<'ctx>,
|
||||
str_symbol: Symbol,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
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,
|
||||
)
|
||||
}
|
||||
|
||||
/// 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,
|
||||
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 bumpalo::collections::Vec;
|
||||
use inkwell::context::Context;
|
||||
|
@ -367,7 +367,6 @@ fn decrement_refcount_builtin<'a, 'ctx, 'env>(
|
|||
List(memory_mode, element_layout) => {
|
||||
let wrapper_struct = value.into_struct_value();
|
||||
if element_layout.contains_refcounted() {
|
||||
use crate::llvm::build_list::{incrementing_elem_loop, load_list};
|
||||
use inkwell::types::BasicType;
|
||||
|
||||
let ptr_type =
|
||||
|
@ -451,7 +450,6 @@ fn increment_refcount_builtin<'a, 'ctx, 'env>(
|
|||
List(memory_mode, element_layout) => {
|
||||
let wrapper_struct = value.into_struct_value();
|
||||
if element_layout.contains_refcounted() {
|
||||
use crate::llvm::build_list::{incrementing_elem_loop, load_list};
|
||||
use inkwell::types::BasicType;
|
||||
|
||||
let ptr_type =
|
||||
|
|
|
@ -490,4 +490,29 @@ mod gen_str {
|
|||
fn str_starts_with_false_small_str() {
|
||||
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,
|
||||
StrSplit,
|
||||
StrCountGraphemes,
|
||||
StrFromInt,
|
||||
ListLen,
|
||||
ListGetUnsafe,
|
||||
ListSet,
|
||||
|
|
|
@ -749,6 +749,7 @@ define_builtins! {
|
|||
6 STR_COUNT_GRAPHEMES: "countGraphemes"
|
||||
7 STR_STARTS_WITH: "startsWith"
|
||||
8 STR_ENDS_WITH: "endsWith"
|
||||
9 STR_FROM_INT: "fromInt"
|
||||
}
|
||||
4 LIST: "List" => {
|
||||
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])
|
||||
}
|
||||
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 {
|
||||
match self {
|
||||
Layout::Builtin(Builtin::List(_, _)) => true,
|
||||
Layout::Builtin(Builtin::List(MemoryMode::Refcounted, _)) => true,
|
||||
Layout::Builtin(Builtin::Str) => true,
|
||||
Layout::RecursiveUnion(_) => true,
|
||||
Layout::RecursivePointer => true,
|
||||
|
@ -477,12 +477,12 @@ impl<'a> Layout<'a> {
|
|||
match self {
|
||||
Builtin(builtin) => builtin.is_refcounted(),
|
||||
PhantomEmptyStruct => false,
|
||||
Struct(fields) => fields.iter().any(|f| f.is_refcounted()),
|
||||
Struct(fields) => fields.iter().any(|f| f.contains_refcounted()),
|
||||
Union(fields) => fields
|
||||
.iter()
|
||||
.map(|ls| ls.iter())
|
||||
.flatten()
|
||||
.any(|f| f.is_refcounted()),
|
||||
.any(|f| f.contains_refcounted()),
|
||||
RecursiveUnion(_) => true,
|
||||
Closure(_, closure_layout, _) => closure_layout.contains_refcounted(),
|
||||
FunctionPointer(_, _) | RecursivePointer | Pointer(_) => false,
|
||||
|
|
|
@ -1851,7 +1851,6 @@ mod test_mono {
|
|||
let Test.2 = S Test.9 Test.8;
|
||||
let Test.5 = 1i64;
|
||||
let Test.6 = Index 0 Test.2;
|
||||
dec Test.2;
|
||||
let Test.7 = lowlevel Eq Test.5 Test.6;
|
||||
if Test.7 then
|
||||
let Test.3 = 0i64;
|
||||
|
@ -1903,15 +1902,12 @@ mod test_mono {
|
|||
let Test.10 = lowlevel Eq Test.8 Test.9;
|
||||
if Test.10 then
|
||||
let Test.4 = Index 1 Test.2;
|
||||
dec Test.2;
|
||||
let Test.3 = 1i64;
|
||||
ret Test.3;
|
||||
else
|
||||
dec Test.2;
|
||||
let Test.5 = 0i64;
|
||||
ret Test.5;
|
||||
else
|
||||
dec Test.2;
|
||||
let Test.6 = 0i64;
|
||||
ret Test.6;
|
||||
"#
|
||||
|
|
|
@ -23,6 +23,8 @@ roc_solve = { path = "../compiler/solve" }
|
|||
roc_mono = { path = "../compiler/mono" }
|
||||
roc_load = { path = "../compiler/load" }
|
||||
roc_gen = { path = "../compiler/gen" }
|
||||
roc_fmt = { path = "../compiler/fmt" }
|
||||
|
||||
roc_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
|
||||
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;
|
||||
|
||||
pub mod ast;
|
||||
pub mod file;
|
||||
mod rect;
|
||||
pub mod text_state;
|
||||
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.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