mirror of
https://github.com/roc-lang/roc.git
synced 2025-12-23 08:48:03 +00:00
Add List.concat
This commit is contained in:
parent
092cf67728
commit
380630c823
6 changed files with 166 additions and 4 deletions
|
|
@ -155,6 +155,9 @@ fn replaceStrIsEmptyWithLowLevel(env: *ModuleEnv) !std.ArrayList(CIR.Def.Idx) {
|
|||
if (env.common.findIdent("Builtin.List.is_empty")) |list_is_empty_ident| {
|
||||
try low_level_map.put(list_is_empty_ident, .list_is_empty);
|
||||
}
|
||||
if (env.common.findIdent("Builtin.List.concat")) |list_concat_ident| {
|
||||
try low_level_map.put(list_concat_ident, .list_concat);
|
||||
}
|
||||
if (env.common.findIdent("list_get_unsafe")) |list_get_unsafe_ident| {
|
||||
try low_level_map.put(list_get_unsafe_ident, .list_get_unsafe);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ Builtin := [].{
|
|||
List := [ProvidedByCompiler].{
|
||||
len : List(_elem) -> U64
|
||||
is_empty : List(_elem) -> Bool
|
||||
concat : List(item), List(item) -> List(item)
|
||||
|
||||
first : List(elem) -> Try(elem, [ListWasEmpty])
|
||||
first = |list| List.get(list, 0)
|
||||
|
|
@ -25,9 +26,6 @@ Builtin := [].{
|
|||
|
||||
keep_if : List(a), (a -> Bool) -> List(a)
|
||||
keep_if = |_, _| []
|
||||
|
||||
concat : List(a), List(a) -> List(a)
|
||||
concat = |_, _| []
|
||||
}
|
||||
|
||||
Bool := [True, False].{
|
||||
|
|
|
|||
|
|
@ -1281,7 +1281,8 @@ pub fn listAllocationPtr(
|
|||
return list.getAllocationDataPtr();
|
||||
}
|
||||
|
||||
fn rcNone(_: ?[*]u8) callconv(.c) void {}
|
||||
/// No-op reference counting function for non-refcounted types
|
||||
pub fn rcNone(_: ?[*]u8) callconv(.c) void {}
|
||||
|
||||
/// Append UTF-8 string bytes to list for efficient string-to-bytes conversion.
|
||||
pub fn listConcatUtf8(
|
||||
|
|
|
|||
|
|
@ -391,6 +391,7 @@ pub const Expr = union(enum) {
|
|||
list_len,
|
||||
list_is_empty,
|
||||
list_get_unsafe,
|
||||
list_concat,
|
||||
|
||||
// Set operations
|
||||
set_is_empty,
|
||||
|
|
|
|||
|
|
@ -2200,6 +2200,66 @@ pub const Interpreter = struct {
|
|||
// Copy to new location and increment refcount
|
||||
return try self.pushCopy(elem_value, roc_ops);
|
||||
},
|
||||
.list_concat => {
|
||||
// List.concat : List(a), List(a) -> List(a)
|
||||
// Args: List(a), List(a)
|
||||
// Returns: List(a) (concatenated list)
|
||||
std.debug.assert(args.len == 2); // low-level .list_concat expects 2 arguments
|
||||
|
||||
const list_a_arg = args[0];
|
||||
const list_b_arg = args[1];
|
||||
|
||||
std.debug.assert(list_a_arg.ptr != null); // low-level .list_concat expects non-null list pointer
|
||||
std.debug.assert(list_b_arg.ptr != null); // low-level .list_concat expects non-null list pointer
|
||||
|
||||
// Extract element layout from List(a)
|
||||
std.debug.assert(list_a_arg.layout.tag == .list or list_a_arg.layout.tag == .list_of_zst);
|
||||
std.debug.assert(list_b_arg.layout.tag == .list or list_b_arg.layout.tag == .list_of_zst);
|
||||
|
||||
const list_a: *const builtins.list.RocList = @ptrCast(@alignCast(list_a_arg.ptr.?));
|
||||
const list_b: *const builtins.list.RocList = @ptrCast(@alignCast(list_b_arg.ptr.?));
|
||||
|
||||
// Get element layout
|
||||
const elem_layout_idx = list_a_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();
|
||||
|
||||
// TODO: Proper refcounting for list elements
|
||||
// For now, we use no-op functions even for refcounted elements.
|
||||
// This works correctly because listConcat will handle the list-level refcounting,
|
||||
// but element-level refcounting may need manual handling in complex cases.
|
||||
const inc_fn = builtins.list.rcNone;
|
||||
const dec_fn = builtins.list.rcNone;
|
||||
|
||||
// Call listConcat - it consumes both input lists
|
||||
const result_list = builtins.list.listConcat(
|
||||
list_a.*,
|
||||
list_b.*,
|
||||
elem_alignment_u32,
|
||||
elem_size,
|
||||
elements_refcounted,
|
||||
inc_fn,
|
||||
dec_fn,
|
||||
roc_ops,
|
||||
);
|
||||
|
||||
// Allocate space for the result list
|
||||
const result_layout = list_a_arg.layout; // Same layout as input
|
||||
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;
|
||||
},
|
||||
.set_is_empty => {
|
||||
// TODO: implement Set.is_empty
|
||||
self.triggerCrash("Set.is_empty not yet implemented", false, roc_ops);
|
||||
|
|
|
|||
|
|
@ -174,3 +174,102 @@ test "e_low_level_lambda - Str.is_empty in conditional" {
|
|||
const tag_name = result.module_env.getIdent(expr.e_zero_argument_tag.ident);
|
||||
try testing.expectEqualStrings("True", tag_name);
|
||||
}
|
||||
|
||||
test "e_low_level_lambda - List.concat with two non-empty lists" {
|
||||
const src =
|
||||
\\x = List.concat([1, 2], [3, 4])
|
||||
\\len = List.len(x)
|
||||
;
|
||||
|
||||
var result = try parseCheckAndEvalModule(src);
|
||||
defer cleanupEvalModule(&result);
|
||||
|
||||
const summary = try result.evaluator.evalAll();
|
||||
|
||||
// Should evaluate 2 declarations with 0 crashes
|
||||
try testing.expectEqual(@as(u32, 2), summary.evaluated);
|
||||
try testing.expectEqual(@as(u32, 0), summary.crashed);
|
||||
|
||||
// Verify the length is 4
|
||||
const defs = result.module_env.store.sliceDefs(result.module_env.all_defs);
|
||||
const len_def = result.module_env.store.getDef(defs[1]);
|
||||
const len_expr = result.module_env.store.getExpr(len_def.expr);
|
||||
|
||||
try testing.expect(len_expr == .e_num);
|
||||
try testing.expectEqual(@as(u64, 4), len_expr.e_num.value.int);
|
||||
}
|
||||
|
||||
test "e_low_level_lambda - List.concat with empty and non-empty list" {
|
||||
const src =
|
||||
\\x = List.concat([], [1, 2, 3])
|
||||
\\len = List.len(x)
|
||||
;
|
||||
|
||||
var result = try parseCheckAndEvalModule(src);
|
||||
defer cleanupEvalModule(&result);
|
||||
|
||||
const summary = try result.evaluator.evalAll();
|
||||
|
||||
// Should evaluate 2 declarations with 0 crashes
|
||||
try testing.expectEqual(@as(u32, 2), summary.evaluated);
|
||||
try testing.expectEqual(@as(u32, 0), summary.crashed);
|
||||
|
||||
// Verify the length is 3
|
||||
const defs = result.module_env.store.sliceDefs(result.module_env.all_defs);
|
||||
const len_def = result.module_env.store.getDef(defs[1]);
|
||||
const len_expr = result.module_env.store.getExpr(len_def.expr);
|
||||
|
||||
try testing.expect(len_expr == .e_num);
|
||||
try testing.expectEqual(@as(u64, 3), len_expr.e_num.value.int);
|
||||
}
|
||||
|
||||
test "e_low_level_lambda - List.concat with two empty lists" {
|
||||
const src =
|
||||
\\x : List(U64)
|
||||
\\x = List.concat([], [])
|
||||
\\len = List.len(x)
|
||||
;
|
||||
|
||||
var result = try parseCheckAndEvalModule(src);
|
||||
defer cleanupEvalModule(&result);
|
||||
|
||||
const summary = try result.evaluator.evalAll();
|
||||
|
||||
// Should evaluate 2 declarations with 0 crashes
|
||||
try testing.expectEqual(@as(u32, 2), summary.evaluated);
|
||||
try testing.expectEqual(@as(u32, 0), summary.crashed);
|
||||
|
||||
// Verify the length is 0
|
||||
const defs = result.module_env.store.sliceDefs(result.module_env.all_defs);
|
||||
const len_def = result.module_env.store.getDef(defs[1]);
|
||||
const len_expr = result.module_env.store.getExpr(len_def.expr);
|
||||
|
||||
try testing.expect(len_expr == .e_num);
|
||||
try testing.expectEqual(@as(u64, 0), len_expr.e_num.value.int);
|
||||
}
|
||||
|
||||
test "e_low_level_lambda - List.concat preserves order" {
|
||||
const src =
|
||||
\\x = List.concat([10, 20], [30, 40, 50])
|
||||
\\first = List.first(x)
|
||||
;
|
||||
|
||||
var result = try parseCheckAndEvalModule(src);
|
||||
defer cleanupEvalModule(&result);
|
||||
|
||||
const summary = try result.evaluator.evalAll();
|
||||
|
||||
// Should evaluate 2 declarations with 0 crashes
|
||||
try testing.expectEqual(@as(u32, 2), summary.evaluated);
|
||||
try testing.expectEqual(@as(u32, 0), summary.crashed);
|
||||
|
||||
// Verify the first element is 10 (wrapped in Try.Ok)
|
||||
const defs = result.module_env.store.sliceDefs(result.module_env.all_defs);
|
||||
const first_def = result.module_env.store.getDef(defs[1]);
|
||||
const first_expr = result.module_env.store.getExpr(first_def.expr);
|
||||
|
||||
// Should be a Try.Ok tag with value 10
|
||||
try testing.expect(first_expr == .e_tag);
|
||||
const tag_name = result.module_env.getIdent(first_expr.e_tag.ident);
|
||||
try testing.expectEqualStrings("Ok", tag_name);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue