diff --git a/Cargo.lock b/Cargo.lock index 9358a3163a..76cacfcbb6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2569,6 +2569,7 @@ dependencies = [ "roc_can", "roc_collections", "roc_constrain", + "roc_fmt", "roc_gen", "roc_load", "roc_module", diff --git a/compiler/builtins/bitcode/src/helpers/grapheme.zig b/compiler/builtins/bitcode/src/helpers/grapheme.zig index 2f1e75d3d6..252873cb4e 100644 --- a/compiler/builtins/bitcode/src/helpers/grapheme.zig +++ b/compiler/builtins/bitcode/src/helpers/grapheme.zig @@ -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` // diff --git a/compiler/builtins/bitcode/src/main.zig b/compiler/builtins/bitcode/src/main.zig index 257bd9e52f..465d1c066e 100644 --- a/compiler/builtins/bitcode/src/main.zig +++ b/compiler/builtins/bitcode/src/main.zig @@ -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 diff --git a/compiler/builtins/bitcode/src/str.zig b/compiler/builtins/bitcode/src/str.zig index 39281d1b33..f7b2c2ba4c 100644 --- a/compiler/builtins/bitcode/src/str.zig +++ b/compiler/builtins/bitcode/src/str.zig @@ -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, diff --git a/compiler/builtins/src/bitcode.rs b/compiler/builtins/src/bitcode.rs index 2fbd4f6782..5ccf0c89aa 100644 --- a/compiler/builtins/src/bitcode.rs +++ b/compiler/builtins/src/bitcode.rs @@ -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"; diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index e6cd26405b..120e78dc58 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -429,6 +429,12 @@ pub fn types() -> MutMap { 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 ]* diff --git a/compiler/builtins/src/unique.rs b/compiler/builtins/src/unique.rs index 45206d462e..406f4d313f 100644 --- a/compiler/builtins/src/unique.rs +++ b/compiler/builtins/src/unique.rs @@ -1108,6 +1108,12 @@ pub fn types() -> MutMap { 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)) diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index 021f14e5f2..7b1d737898 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -56,6 +56,7 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap { 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(); diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index b428c28491..0c87627943 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -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 diff --git a/compiler/gen/src/llvm/build_list.rs b/compiler/gen/src/llvm/build_list.rs index 9c6c20bbb7..4049f38e05 100644 --- a/compiler/gen/src/llvm/build_list.rs +++ b/compiler/gen/src/llvm/build_list.rs @@ -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,104 +1411,50 @@ 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; - let builder = env.builder; + ( + BasicValueEnum::StructValue(ptr_and_data), + Layout::Closure(_, closure_layout, ret_elem_layout), + ) => { + let builder = env.builder; - let func_ptr = builder - .build_extract_value(ptr_and_data, 0, "function_ptr") - .unwrap() - .into_pointer_value(); + let func_ptr = builder + .build_extract_value(ptr_and_data, 0, "function_ptr") + .unwrap() + .into_pointer_value(); - let closure_data = builder - .build_extract_value(ptr_and_data, 1, "closure_data") - .unwrap(); + let closure_data = builder + .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( - 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") + list_map_help!( + env, + layout_ids, + inplace, + parent, + func, + func_layout, + list, + list_layout, + func_ptr, + ret_elem_layout, + Some((&closure_data_layout, closure_data)) + ) } _ => { unreachable!( diff --git a/compiler/gen/src/llvm/build_str.rs b/compiler/gen/src/llvm/build_str.rs index 209a125b13..3a021dd10d 100644 --- a/compiler/gen/src/llvm/build_str.rs +++ b/compiler/gen/src/llvm/build_str.rs @@ -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() +} diff --git a/compiler/gen/src/llvm/refcounting.rs b/compiler/gen/src/llvm/refcounting.rs index 60e252e075..0ef67b4960 100644 --- a/compiler/gen/src/llvm/refcounting.rs +++ b/compiler/gen/src/llvm/refcounting.rs @@ -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 = diff --git a/compiler/gen/tests/gen_str.rs b/compiler/gen/tests/gen_str.rs index d45070a879..027471be3c 100644 --- a/compiler/gen/tests/gen_str.rs +++ b/compiler/gen/tests/gen_str.rs @@ -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); + } } diff --git a/compiler/module/src/low_level.rs b/compiler/module/src/low_level.rs index 887e836952..db9617ded4 100644 --- a/compiler/module/src/low_level.rs +++ b/compiler/module/src/low_level.rs @@ -9,6 +9,7 @@ pub enum LowLevel { StrEndsWith, StrSplit, StrCountGraphemes, + StrFromInt, ListLen, ListGetUnsafe, ListSet, diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 8d05487f96..c434a01c4c 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -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 diff --git a/compiler/mono/src/borrow.rs b/compiler/mono/src/borrow.rs index b9cef5a4ba..b100625fbc 100644 --- a/compiler/mono/src/borrow.rs +++ b/compiler/mono/src/borrow.rs @@ -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]), } } diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index c54e39564f..91b2e3986e 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -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, diff --git a/compiler/mono/tests/test_mono.rs b/compiler/mono/tests/test_mono.rs index c7d7ba58d6..2f2f834362 100644 --- a/compiler/mono/tests/test_mono.rs +++ b/compiler/mono/tests/test_mono.rs @@ -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; "# diff --git a/editor/Cargo.toml b/editor/Cargo.toml index 4587d2ce80..7dec086b0e 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -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! diff --git a/editor/src/file.rs b/editor/src/file.rs new file mode 100644 index 0000000000..da8328fdaf --- /dev/null +++ b/editor/src/file.rs @@ -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>>, +} + +#[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, 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) + } +} diff --git a/editor/src/lib.rs b/editor/src/lib.rs index d0b5ef881b..7a5c178d00 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -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; diff --git a/editor/tests/modules/Simple.roc b/editor/tests/modules/Simple.roc new file mode 100644 index 0000000000..0b083ca390 --- /dev/null +++ b/editor/tests/modules/Simple.roc @@ -0,0 +1,26 @@ +interface Simple + exposes [ + v, x + ] + imports [] + + + + + + + + + +v : Str + + + + + +v = "Value!" + + + +x : Int +x = 4 \ No newline at end of file diff --git a/editor/tests/modules/Storage.roc b/editor/tests/modules/Storage.roc new file mode 100644 index 0000000000..950d1d01f1 --- /dev/null +++ b/editor/tests/modules/Storage.roc @@ -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) + diff --git a/editor/tests/test_file.rs b/editor/tests/test_file.rs new file mode 100644 index 0000000000..e5a8059c2c --- /dev/null +++ b/editor/tests/test_file.rs @@ -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"# + ) + ); + } +} diff --git a/examples/effect/Main.roc b/examples/effect/Main.roc index 59aaeedf94..9295df2d36 100644 --- a/examples/effect/Main.roc +++ b/examples/effect/Main.roc @@ -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" +#