diff --git a/src/build/builtin_compiler/main.zig b/src/build/builtin_compiler/main.zig index 3b4bd445c7..87b0c462a3 100644 --- a/src/build/builtin_compiler/main.zig +++ b/src/build/builtin_compiler/main.zig @@ -166,6 +166,12 @@ fn replaceStrIsEmptyWithLowLevel(env: *ModuleEnv) !std.ArrayList(CIR.Def.Idx) { if (env.common.findIdent("list_get_unsafe")) |list_get_unsafe_ident| { try low_level_map.put(list_get_unsafe_ident, .list_get_unsafe); } + if (env.common.findIdent("Builtin.List.drop_at")) |list_drop_at_ident| { + try low_level_map.put(list_drop_at_ident, .list_drop_at); + } + if (env.common.findIdent("Builtin.List.sublist")) |list_sublist_ident| { + try low_level_map.put(list_sublist_ident, .list_sublist); + } if (env.common.findIdent("Builtin.Bool.is_eq")) |bool_is_eq_ident| { try low_level_map.put(bool_is_eq_ident, .bool_is_eq); } diff --git a/src/build/roc/Builtin.roc b/src/build/roc/Builtin.roc index a6a6754e7a..dcdf0f5452 100644 --- a/src/build/roc/Builtin.roc +++ b/src/build/roc/Builtin.roc @@ -132,6 +132,9 @@ Builtin :: [].{ True } + drop_at : List(a), U64 -> List(a) + + sublist : List(a), {start : U64, len : U64} -> List(a) } Bool := [False, True].{ diff --git a/src/canonicalize/Expression.zig b/src/canonicalize/Expression.zig index b00791e377..1523fe3e6f 100644 --- a/src/canonicalize/Expression.zig +++ b/src/canonicalize/Expression.zig @@ -475,6 +475,8 @@ pub const Expr = union(enum) { list_concat, list_with_capacity, list_sort_with, + list_drop_at, + list_sublist, // Bool operations bool_is_eq, diff --git a/src/canonicalize/ModuleEnv.zig b/src/canonicalize/ModuleEnv.zig index 47bc4488c3..6985b99b09 100644 --- a/src/canonicalize/ModuleEnv.zig +++ b/src/canonicalize/ModuleEnv.zig @@ -163,6 +163,9 @@ pub const CommonIdents = extern struct { // from_utf8 error payload fields (BadUtf8 record) problem: Ident.Idx, index: Ident.Idx, + // sublist argument payload fields + // sublist_start: Ident.Idx, + // sublist_len: Ident.Idx, /// Insert all well-known identifiers into a CommonEnv. /// Use this when creating a fresh ModuleEnv from scratch. @@ -228,6 +231,9 @@ pub const CommonIdents = extern struct { // from_utf8 error payload fields (BadUtf8 record) .problem = try common.insertIdent(gpa, Ident.for_text("problem")), .index = try common.insertIdent(gpa, Ident.for_text("index")), + // sublist argument payload fields + // .sublist_start = try common.insertIdent(gpa, Ident.for_text("start")), + // .sublist_len = try common.insertIdent(gpa, Ident.for_text("len")), }; } @@ -296,6 +302,9 @@ pub const CommonIdents = extern struct { // from_utf8 error payload fields (BadUtf8 record) .problem = common.findIdent("problem") orelse unreachable, .index = common.findIdent("index") orelse unreachable, + // sublist argument payload fields + // .sublist_start = common.findIdent("start") orelse unreachable, + // .sublist_len = common.findIdent("len") orelse unreachable, }; } }; diff --git a/src/eval/interpreter.zig b/src/eval/interpreter.zig index e233e60349..50c76a27a9 100644 --- a/src/eval/interpreter.zig +++ b/src/eval/interpreter.zig @@ -1729,6 +1729,122 @@ pub const Interpreter = struct { out.is_initialized = true; return out; }, + .list_drop_at => { + // List.drop_at : List(a), U64 -> List(a) + std.debug.assert(args.len == 2); // low-level .list_drop_at expects 2 argument + + const list_arg = args[0]; + const drop_index_arg = args[1]; + const drop_index: u64 = @intCast(drop_index_arg.asI128()); + + std.debug.assert(list_arg.layout.tag == .list or list_arg.layout.tag == .list_of_zst); + + const roc_list: *const builtins.list.RocList = @ptrCast(@alignCast(list_arg.ptr.?)); + + // Get element layout from the list layout + const elem_layout_idx = list_arg.layout.data.list; + const elem_layout = self.runtime_layout_store.getLayout(elem_layout_idx); + const elem_size = self.runtime_layout_store.layoutSize(elem_layout); + const elem_alignment = elem_layout.alignment(self.runtime_layout_store.targetUsize()).toByteUnits(); + const elem_alignment_u32: u32 = @intCast(elem_alignment); + + // Determine if elements are refcounted + const elements_refcounted = elem_layout.isRefcounted(); + + // Set up context for refcount callbacks + var refcount_context = RefcountContext{ + .layout_store = &self.runtime_layout_store, + .elem_layout = elem_layout, + .roc_ops = roc_ops, + }; + + // Return list with element at index dropped + const result_list = builtins.list.listDropAt( + roc_list.*, + elem_alignment_u32, + elem_size, + elements_refcounted, + drop_index, + if (elements_refcounted) @ptrCast(&refcount_context) else null, + if (elements_refcounted) &listElementInc else &builtins.list.rcNone, + if (elements_refcounted) @ptrCast(&refcount_context) else null, + if (elements_refcounted) &listElementDec else &builtins.list.rcNone, + roc_ops, + ); + + // Allocate space for the result list + const result_layout = list_arg.layout; + var out = try self.pushRaw(result_layout, 0); + out.is_initialized = false; + + // Copy the result list structure to the output + const result_ptr: *builtins.list.RocList = @ptrCast(@alignCast(out.ptr.?)); + result_ptr.* = result_list; + + out.is_initialized = true; + return out; + }, + .list_sublist => { + // List.sublist : List(a), {start : U64, len : U64} -> List(a) + std.debug.assert(args.len == 2); // low-level .list_sublist expects 2 argument + + // Check and extract first element as a typed RocList + const list_arg = args[0]; + std.debug.assert(list_arg.layout.tag == .list or list_arg.layout.tag == .list_of_zst); + const roc_list: *const builtins.list.RocList = @ptrCast(@alignCast(list_arg.ptr.?)); + + // Access second argument as a record and extract its specific fields + const sublist_config = args[1].asRecord(&self.runtime_layout_store) catch unreachable; + const sublist_start_index = 0; // sublist_config.findFieldIndex(self.env.idents.sublist_start).?; + const sublist_start_stack = sublist_config.getFieldByIndex(sublist_start_index) catch unreachable; + const sublist_len_index = 1; //sublist_config.findFieldIndex(self.env.idents.sublist_len).?; + const sublist_len_stack = sublist_config.getFieldByIndex(sublist_len_index) catch unreachable; + const sublist_start: u64 = @intCast(sublist_start_stack.asI128()); + const sublist_len: u64 = @intCast(sublist_len_stack.asI128()); + std.debug.print("\nConfig Record: {{start: {d}, len: {d} }}\n", .{ sublist_start, sublist_len }); + + // Get element layout from the list layout + const elem_layout_idx = list_arg.layout.data.list; + const elem_layout = self.runtime_layout_store.getLayout(elem_layout_idx); + const elem_size = self.runtime_layout_store.layoutSize(elem_layout); + const elem_alignment = elem_layout.alignment(self.runtime_layout_store.targetUsize()).toByteUnits(); + const elem_alignment_u32: u32 = @intCast(elem_alignment); + + // Determine if elements are refcounted + const elements_refcounted = elem_layout.isRefcounted(); + + // Set up context for refcount callbacks + var refcount_context = RefcountContext{ + .layout_store = &self.runtime_layout_store, + .elem_layout = elem_layout, + .roc_ops = roc_ops, + }; + + // Return list with element at index dropped + const result_list = builtins.list.listSublist( + roc_list.*, + elem_alignment_u32, + elem_size, + elements_refcounted, + sublist_start, + sublist_len, + if (elements_refcounted) @ptrCast(&refcount_context) else null, + if (elements_refcounted) &listElementDec else &builtins.list.rcNone, + roc_ops, + ); + + // Allocate space for the result list + const result_layout = list_arg.layout; + var out = try self.pushRaw(result_layout, 0); + out.is_initialized = false; + + // Copy the result list structure to the output + const result_ptr: *builtins.list.RocList = @ptrCast(@alignCast(out.ptr.?)); + result_ptr.* = result_list; + + out.is_initialized = true; + return out; + }, // Bool operations .bool_is_eq => { // Bool.is_eq : Bool, Bool -> Bool diff --git a/src/eval/test/list_refcount_builtins.zig b/src/eval/test/list_refcount_builtins.zig index 33b95f3678..0091b4f8ee 100644 --- a/src/eval/test/list_refcount_builtins.zig +++ b/src/eval/test/list_refcount_builtins.zig @@ -52,6 +52,13 @@ test "list refcount builtins - phase 12 limitation documented" { // - "e_low_level_lambda - List.with_capacity without capacity, of str (refcounted elements) can concat" // - "e_low_level_lambda - List.with_capacity of zero-sized type creates empty list" // +// - "e_low_level_lambda - List.drop_at on an empty list at index 0" +// - "e_low_level_lambda - List.drop_at on an empty list at index >0" +// - "e_low_level_lambda - List.drop_at on non-empty list" +// - "e_low_level_lambda - List.drop_at out of bounds on non-empty list" +// - "e_low_level_lambda - List.drop_at on refcounted List(Str)" +// - "e_low_level_lambda - List.drop_at on refcounted List(List(Str))" +// // interpreter_style_test.zig: // - "interpreter: match list pattern destructures" // - "interpreter: match list rest binds slice" diff --git a/src/eval/test/low_level_interp_test.zig b/src/eval/test/low_level_interp_test.zig index 889f7e1c54..6d76fef66c 100644 --- a/src/eval/test/low_level_interp_test.zig +++ b/src/eval/test/low_level_interp_test.zig @@ -755,6 +755,94 @@ test "e_low_level_lambda - List.with_capacity of zero-sized type creates empty l try testing.expectEqual(@as(i128, 0), len_value); } +test "e_low_level_lambda - List.drop_at on an empty list at index 0" { + const src = + \\x = List.drop_at([], 0) + \\len = List.len(x) + ; + + const len_value = try evalModuleAndGetInt(src, 1); + try testing.expectEqual(@as(i128, 0), len_value); +} + +test "e_low_level_lambda - List.drop_at on an empty list at index >0" { + const src = + \\x = List.drop_at([], 10) + \\len = List.len(x) + ; + + const len_value = try evalModuleAndGetInt(src, 1); + try testing.expectEqual(@as(i128, 0), len_value); +} + +test "e_low_level_lambda - List.drop_at on non-empty list" { + const src = + \\x = List.drop_at([1, 2, 3], 0) + \\len = List.len(x) + \\first = List.get(x, 0) + ; + + const len_value = try evalModuleAndGetInt(src, 1); + try testing.expectEqual(@as(i128, 2), len_value); + + const value = try evalModuleAndGetString(src, 2, test_allocator); + defer test_allocator.free(value); + try testing.expectEqualStrings("Ok(2)", value); +} + +test "e_low_level_lambda - List.drop_at out of bounds on non-empty list" { + const src = + \\x = List.drop_at([1, 2, 3, 4, 5], 10) + \\len = List.len(x) + ; + + const len_value = try evalModuleAndGetInt(src, 1); + try testing.expectEqual(@as(i128, 5), len_value); +} + +test "e_low_level_lambda - List.drop_at on refcounted List(Str)" { + const src = + \\x = List.drop_at(["cat", "chases", "rat"], 1) + \\len = List.len(x) + \\second = List.get(x, 1) + ; + + const len_value = try evalModuleAndGetInt(src, 1); + try testing.expectEqual(@as(i128, 2), len_value); + + const value = try evalModuleAndGetString(src, 2, test_allocator); + defer test_allocator.free(value); + try testing.expectEqualStrings("Ok(\"rat\")", value); +} + +test "e_low_level_lambda - List.drop_at on refcounted List(List(Str))" { + const src = + \\x = List.drop_at([["two", "words"], [], ["a", "four", "word", "list"]], 1) + \\len = List.len(x) + \\second = Try.ok_or(List.get(x, 1), []) + \\elt_len = List.len(second) + ; + + const len_value = try evalModuleAndGetInt(src, 1); + try testing.expectEqual(@as(i128, 2), len_value); + + const elt_len_value = try evalModuleAndGetInt(src, 3); + try testing.expectEqual(@as(i128, 4), elt_len_value); +} + +test "e_low_level_lambda - List.sublist on non-empty list" { + const src = + \\x = List.sublist([0, 1, 2, 3, 4], {len: 10, start: 3}) + \\len = List.len(x) + ; + + const len_value = try evalModuleAndGetInt(src, 1); + try testing.expectEqual(@as(i128, 2), len_value); + + // const elt_len_value = try evalModuleAndGetInt(src, 3); + // try testing.expectEqual(@as(i128, 4), elt_len_value); +} + test "e_low_level_lambda - Dec.to_str returns string representation of decimal" { const src = \\a : Dec