Merge branch 'trunk' into task_can

This commit is contained in:
rvcas 2020-12-04 20:03:27 -05:00
commit 0c8260d71a
25 changed files with 565 additions and 211 deletions

1
Cargo.lock generated
View file

@ -2569,6 +2569,7 @@ dependencies = [
"roc_can",
"roc_collections",
"roc_constrain",
"roc_fmt",
"roc_gen",
"roc_load",
"roc_module",

View file

@ -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`
//

View file

@ -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

View file

@ -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,

View file

@ -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";

View file

@ -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 ]*

View file

@ -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))

View file

@ -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();

View file

@ -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

View file

@ -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!(

View file

@ -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()
}

View file

@ -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 =

View file

@ -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);
}
}

View file

@ -9,6 +9,7 @@ pub enum LowLevel {
StrEndsWith,
StrSplit,
StrCountGraphemes,
StrFromInt,
ListLen,
ListGetUnsafe,
ListSet,

View file

@ -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

View file

@ -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]),
}
}

View file

@ -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,

View file

@ -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;
"#

View file

@ -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
View 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)
}
}

View file

@ -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;

View file

@ -0,0 +1,26 @@
interface Simple
exposes [
v, x
]
imports []
v : Str
v = "Value!"
x : Int
x = 4

View 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
View 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"#
)
);
}
}

View file

@ -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"
#