feature. See also
. The project being documented here (as the example) is the Zig library itself.
File
Code
const std = @import("std");
const builtin = @import("builtin");
const assert = std.debug.assert;
const meta = std.meta;
const mem = std.mem;
const Allocator = mem.Allocator;
const testing = std.testing;
pub fn MultiArrayList(comptime T: type) type {
return struct {
bytes: [*]u8 = undefined,
len: usize = 0,
capacity: usize = 0,
pub const empty: Self = .{
.bytes = undefined,
.len = 0,
.capacity = 0,
};
pub fn initCapacity(gpa: Allocator, num: usize) Allocator.Error!Self {
var self: Self = .empty;
try self.setCapacity(gpa, num);
return self;
}
const Elem = switch (@typeInfo(T)) {
.@"struct" => T,
.@"union" => |u| struct {
pub const Bare = std.meta.BareUnion(T);
pub const Tag =
u.tag_type orelse @compileError("MultiArrayList does not support untagged unions");
tags: Tag,
data: Bare,
pub fn fromT(outer: T) @This() {
const tag = meta.activeTag(outer);
return .{
.tags = tag,
.data = switch (tag) {
inline else => |t| @unionInit(Bare, @tagName(t), @field(outer, @tagName(t))),
},
};
}
pub fn toT(tag: Tag, bare: Bare) T {
return switch (tag) {
inline else => |t| @unionInit(T, @tagName(t), @field(bare, @tagName(t))),
};
}
},
else => @compileError("MultiArrayList only supports structs and tagged unions"),
};
pub const Field = meta.FieldEnum(Elem);
pub const Slice = struct {
ptrs: [field_names.len][*]u8,
len: usize,
capacity: usize,
pub const empty: Slice = .{
.ptrs = undefined,
.len = 0,
.capacity = 0,
};
pub fn items(self: Slice, comptime field: Field) []FieldType(field) {
const F = FieldType(field);
if (self.capacity == 0) {
return &[_]F{};
}
const byte_ptr = self.ptrs[@backingInt(field)];
const casted_ptr: [*]F = if (@sizeOf(F) == 0)
undefined
else
@ptrCast(@alignCast(byte_ptr));
return casted_ptr[0..self.len];
}
pub fn set(self: *Slice, index: usize, elem: T) void {
const e = switch (@typeInfo(T)) {
.@"struct" => elem,
.@"union" => Elem.fromT(elem),
else => unreachable,
};
inline for (field_names, 0..) |field_name, i| {
self.items(@as(Field, @fromBackingInt(@intCast(i))))[index] = @field(e, field_name);
}
}
pub fn get(self: Slice, index: usize) T {
var result: Elem = undefined;
inline for (field_names, 0..) |field_name, i| {
@field(result, field_name) = self.items(@as(Field, @fromBackingInt(@intCast(i))))[index];
}
return switch (@typeInfo(T)) {
.@"struct" => result,
.@"union" => Elem.toT(result.tags, result.data),
else => unreachable,
};
}
pub fn swap(self: Slice, a: usize, b: usize) void {
inline for (@typeInfo(Field).@"enum".field_names) |field_name| {
const its = self.items(@field(Field, field_name));
std.mem.swap(@FieldType(T, field_name), &its[a], &its[b]);
}
}
pub fn toMultiArrayList(self: Slice) Self {
if (self.ptrs.len == 0 or self.capacity == 0) {
return .{};
}
return .{
.bytes = self.ptrs[sizes.fields[0]],
.len = self.len,
.capacity = self.capacity,
};
}
pub fn deinit(self: *Slice, gpa: Allocator) void {
var other = self.toMultiArrayList();
other.deinit(gpa);
self.* = undefined;
}
pub fn subslice(s: Slice, off: usize, len: usize) Slice {
assert(off + len <= s.len);
var ptrs: [field_names.len][*]u8 = undefined;
inline for (s.ptrs, &ptrs, field_types) |in, *out, field_type| {
out.* = in + (off * @sizeOf(field_type));
}
return .{
.ptrs = ptrs,
.len = len,
.capacity = len,
};
}
fn dbHelper(self: *Slice, child: *Elem, field: *Field, entry: *Entry) void {
_ = self;
_ = child;
_ = field;
_ = entry;
}
};
const Self = @This();
const field_names = @typeInfo(Elem).@"struct".field_names;
const field_types = @typeInfo(Elem).@"struct".field_types;
const field_attrs = @typeInfo(Elem).@"struct".field_attrs;
const sizes = blk: {
const Data = struct {
size: usize,
size_index: usize,
alignment: usize,
};
var data: [field_names.len]Data = undefined;
var big_align: usize = 1;
for (field_types, field_attrs, 0..) |f_type, f_attrs, i| {
data[i] = .{
.size = @sizeOf(f_type),
.size_index = i,
.alignment = f_attrs.@"align" orelse @alignOf(f_type),
};
big_align = @max(big_align, data[i].alignment);
}
const Sort = struct {
fn lessThan(context: void, lhs: Data, rhs: Data) bool {
_ = context;
return lhs.alignment > rhs.alignment;
}
};
@setEvalBranchQuota(3 * field_names.len * std.math.log2(field_names.len));
mem.sort(Data, &data, {}, Sort.lessThan);
var sizes_bytes: [field_names.len]usize = undefined;
var field_indexes: [field_names.len]usize = undefined;
for (data, 0..) |elem, i| {
sizes_bytes[i] = elem.size;
field_indexes[i] = elem.size_index;
}
break :blk .{
.bytes = sizes_bytes,
.fields = field_indexes,
.big_align = mem.Alignment.fromByteUnits(big_align),
};
};
pub fn deinit(self: *Self, gpa: Allocator) void {
gpa.free(self.allocatedBytes());
self.* = undefined;
}
pub fn toOwnedSlice(self: *Self) Slice {
const result = self.slice();
self.* = .{};
return result;
}
pub fn slice(self: Self) Slice {
var result: Slice = .{
.ptrs = undefined,
.len = self.len,
.capacity = self.capacity,
};
var ptr: [*]u8 = self.bytes;
for (sizes.bytes, sizes.fields) |field_size, i| {
result.ptrs[i] = ptr;
ptr += field_size * self.capacity;
}
return result;
}
pub fn items(self: Self, comptime field: Field) []FieldType(field) {
return self.slice().items(field);
}
pub fn set(self: *Self, index: usize, elem: T) void {
var slices = self.slice();
slices.set(index, elem);
}
pub fn get(self: Self, index: usize) T {
return self.slice().get(index);
}
pub fn swap(self: Self, a: usize, b: usize) void {
return self.slice().swap(a, b);
}
pub fn append(self: *Self, gpa: Allocator, elem: T) Allocator.Error!void {
try self.ensureUnusedCapacity(gpa, 1);
self.appendAssumeCapacity(elem);
}
pub fn appendAssumeCapacity(self: *Self, elem: T) void {
assert(self.len < self.capacity);
self.len += 1;
self.set(self.len - 1, elem);
}
pub fn appendBounded(self: *Self, elem: T) error{OutOfMemory}!void {
if (self.capacity - self.len < 1) return error.OutOfMemory;
return appendAssumeCapacity(self, elem);
}
pub fn addOne(self: *Self, gpa: Allocator) Allocator.Error!usize {
try self.ensureUnusedCapacity(gpa, 1);
return self.addOneAssumeCapacity();
}
pub fn addOneAssumeCapacity(self: *Self) usize {
assert(self.len < self.capacity);
const index = self.len;
self.len += 1;
return index;
}
pub fn addOneBounded(self: *Self) error{OutOfMemory}!usize {
if (self.capacity - self.len < 1) return error.OutOfMemory;
return addOneAssumeCapacity(self);
}
pub fn pop(self: *Self) ?T {
if (self.len == 0) return null;
const val = self.get(self.len - 1);
self.len -= 1;
return val;
}
pub fn insert(self: *Self, gpa: Allocator, index: usize, elem: T) !void {
try self.ensureUnusedCapacity(gpa, 1);
self.insertAssumeCapacity(index, elem);
}
pub fn insertAssumeCapacity(self: *Self, index: usize, elem: T) void {
assert(self.len < self.capacity);
assert(index <= self.len);
self.len += 1;
const entry = switch (@typeInfo(T)) {
.@"struct" => elem,
.@"union" => Elem.fromT(elem),
else => unreachable,
};
const slices = self.slice();
inline for (field_names, 0..) |field_name, field_index| {
const field_slice = slices.items(@as(Field, @fromBackingInt(@intCast(field_index))));
var i: usize = self.len - 1;
while (i > index) : (i -= 1) {
field_slice[i] = field_slice[i - 1];
}
field_slice[index] = @field(entry, field_name);
}
}
pub fn insertBounded(self: *Self, index: usize, elem: T) error{OutOfMemory}!void {
if (self.capacity - self.len < 1) return error.OutOfMemory;
return insertAssumeCapacity(self, index, elem);
}
pub fn swapRemove(self: *Self, index: usize) void {
const slices = self.slice();
inline for (field_names, 0..) |_, i| {
const field_slice = slices.items(@as(Field, @fromBackingInt(@intCast(i))));
field_slice[index] = field_slice[self.len - 1];
field_slice[self.len - 1] = undefined;
}
self.len -= 1;
}
pub fn orderedRemove(self: *Self, index: usize) void {
const slices = self.slice();
inline for (field_names, 0..) |_, field_index| {
const field_slice = slices.items(@as(Field, @fromBackingInt(@intCast(field_index))));
var i = index;
while (i < self.len - 1) : (i += 1) {
field_slice[i] = field_slice[i + 1];
}
field_slice[i] = undefined;
}
self.len -= 1;
}
pub fn orderedRemoveMany(self: *Self, sorted_indexes: []const usize) void {
if (sorted_indexes.len == 0) return;
const slices = self.slice();
var shift: usize = 1;
for (sorted_indexes[0 .. sorted_indexes.len - 1], sorted_indexes[1..]) |removed, end| {
if (removed == end) continue;
const start = removed + 1;
const len = end - start;
inline for (field_names, 0..) |_, field_index| {
const field_slice = slices.items(@fromBackingInt(@intCast(field_index)));
@memmove(field_slice[start - shift ..][0..len], field_slice[start..][0..len]);
}
shift += 1;
}
const start = sorted_indexes[sorted_indexes.len - 1] + 1;
const end = self.len;
const len = end - start;
inline for (field_names, 0..) |_, field_index| {
const field_slice = slices.items(@fromBackingInt(@intCast(field_index)));
@memmove(field_slice[start - shift ..][0..len], field_slice[start..][0..len]);
}
self.len = end - shift;
}
pub fn resize(self: *Self, gpa: Allocator, new_len: usize) Allocator.Error!void {
try self.ensureTotalCapacity(gpa, new_len);
self.len = new_len;
}
pub fn shrinkAndFree(self: *Self, gpa: Allocator, new_len: usize) void {
if (new_len == 0) return clearAndFree(self, gpa);
assert(new_len <= self.capacity);
assert(new_len <= self.len);
const other_bytes = gpa.alignedAlloc(u8, sizes.big_align, capacityInBytes(new_len)) catch {
const self_slice = self.slice();
inline for (field_types, 0..) |field_type, i| {
if (@sizeOf(field_type) != 0) {
const field = @as(Field, @fromBackingInt(@intCast(i)));
const dest_slice = self_slice.items(field)[new_len..];
// valgrind-enabled builds. Otherwise the valgrind client request
// will be repeated for every element.
@memset(dest_slice, undefined);
}
}
self.len = new_len;
return;
};
var other = Self{
.bytes = other_bytes.ptr,
.capacity = new_len,
.len = new_len,
};
self.len = new_len;
const self_slice = self.slice();
const other_slice = other.slice();
inline for (field_types, 0..) |field_type, i| {
if (@sizeOf(field_type) != 0) {
const field = @as(Field, @fromBackingInt(@intCast(i)));
@memcpy(other_slice.items(field), self_slice.items(field));
}
}
gpa.free(self.allocatedBytes());
self.* = other;
}
pub fn clearAndFree(self: *Self, gpa: Allocator) void {
gpa.free(self.allocatedBytes());
self.* = .{};
}
pub fn shrinkRetainingCapacity(self: *Self, new_len: usize) void {
self.len = new_len;
}
pub fn clearRetainingCapacity(self: *Self) void {
self.len = 0;
}
pub fn ensureTotalCapacity(self: *Self, gpa: Allocator, new_capacity: usize) Allocator.Error!void {
if (self.capacity >= new_capacity) return;
return self.setCapacity(gpa, growCapacity(new_capacity));
}
const init_capacity: comptime_int = init: {
var max: comptime_int = 1;
for (field_types) |field_type| max = @max(max, @sizeOf(field_type));
break :init @max(1, std.atomic.cache_line / max);
};
pub fn growCapacity(minimum: usize) usize {
return minimum +| (minimum / 2 + init_capacity);
}
pub fn ensureUnusedCapacity(self: *Self, gpa: Allocator, additional_count: usize) Allocator.Error!void {
return self.ensureTotalCapacity(gpa, self.len + additional_count);
}
pub fn setCapacity(self: *Self, gpa: Allocator, new_capacity: usize) Allocator.Error!void {
assert(new_capacity >= self.len);
const new_bytes = try gpa.alignedAlloc(u8, sizes.big_align, capacityInBytes(new_capacity));
if (self.len == 0) {
gpa.free(self.allocatedBytes());
self.bytes = new_bytes.ptr;
self.capacity = new_capacity;
return;
}
var other = Self{
.bytes = new_bytes.ptr,
.capacity = new_capacity,
.len = self.len,
};
const self_slice = self.slice();
const other_slice = other.slice();
inline for (field_types, 0..) |field_type, i| {
if (@sizeOf(field_type) != 0) {
const field = @as(Field, @fromBackingInt(@intCast(i)));
@memcpy(other_slice.items(field), self_slice.items(field));
}
}
gpa.free(self.allocatedBytes());
self.* = other;
}
pub fn clone(self: Self, gpa: Allocator) Allocator.Error!Self {
var result = Self{};
errdefer result.deinit(gpa);
try result.ensureTotalCapacity(gpa, self.len);
result.len = self.len;
const self_slice = self.slice();
const result_slice = result.slice();
inline for (field_types, 0..) |field_type, i| {
if (@sizeOf(field_type) != 0) {
const field = @as(Field, @fromBackingInt(@intCast(i)));
@memcpy(result_slice.items(field), self_slice.items(field));
}
}
return result;
}
fn sortInternal(self: Self, a: usize, b: usize, ctx: anytype, comptime mode: std.sort.Mode) void {
const sort_context: struct {
sub_ctx: @TypeOf(ctx),
slice: Slice,
pub fn swap(sc: @This(), a_index: usize, b_index: usize) void {
inline for (field_types, 0..) |field_type, i| {
if (@sizeOf(field_type) != 0) {
const field: Field = @fromBackingInt(@intCast(i));
const ptr = sc.slice.items(field);
mem.swap(field_type, &ptr[a_index], &ptr[b_index]);
}
}
}
pub fn lessThan(sc: @This(), a_index: usize, b_index: usize) bool {
return sc.sub_ctx.lessThan(a_index, b_index);
}
} = .{
.sub_ctx = ctx,
.slice = self.slice(),
};
switch (mode) {
.stable => mem.sortContext(a, b, sort_context),
.unstable => mem.sortUnstableContext(a, b, sort_context),
}
}
pub fn sort(self: Self, ctx: anytype) void {
self.sortInternal(0, self.len, ctx, .stable);
}
pub fn sortSpan(self: Self, a: usize, b: usize, ctx: anytype) void {
self.sortInternal(a, b, ctx, .stable);
}
pub fn sortUnstable(self: Self, ctx: anytype) void {
self.sortInternal(0, self.len, ctx, .unstable);
}
pub fn sortSpanUnstable(self: Self, a: usize, b: usize, ctx: anytype) void {
self.sortInternal(a, b, ctx, .unstable);
}
pub fn capacityInBytes(capacity: usize) usize {
comptime var elem_bytes: usize = 0;
inline for (sizes.bytes) |size| elem_bytes += size;
return elem_bytes * capacity;
}
fn allocatedBytes(self: Self) []align(sizes.big_align.toByteUnits()) u8 {
return @alignCast(self.bytes[0..capacityInBytes(self.capacity)]);
}
fn FieldType(comptime field: Field) type {
return @FieldType(Elem, @tagName(field));
}
const Entry = entry: {
var entry_field_names: [field_names.len][]const u8 = undefined;
var entry_field_types: [field_names.len]type = undefined;
var entry_field_attrs: [field_names.len]std.builtin.Type.Struct.FieldAttributes = undefined;
for (sizes.fields, &entry_field_names, &entry_field_types, &entry_field_attrs) |i, *name, *Type, *attrs| {
name.* = field_names[i] ++ "_ptr";
Type.* = *field_types[i];
attrs.* = .{
.@"comptime" = field_attrs[i].@"comptime",
.@"align" = field_attrs[i].@"align",
};
}
break :entry @Struct(.@"extern", null, &entry_field_names, &entry_field_types, &entry_field_attrs);
};
fn dbHelper(self: *Self, child: *Elem, field: *Field, entry: *Entry) void {
_ = self;
_ = child;
_ = field;
_ = entry;
}
comptime {
if (builtin.zig_backend == .stage2_llvm and !builtin.strip_debug_info) {
_ = &dbHelper;
_ = &Slice.dbHelper;
}
}
};
}
test "basic usage" {
const ally = testing.allocator;
const Foo = struct {
a: u32,
b: []const u8,
c: u8,
};
var list: MultiArrayList(Foo) = .empty;
defer list.deinit(ally);
try testing.expectEqual(@as(usize, 0), list.items(.a).len);
try list.ensureTotalCapacity(ally, 2);
list.appendAssumeCapacity(.{
.a = 1,
.b = "foobar",
.c = 'a',
});
try list.appendBounded(.{
.a = 2,
.b = "zigzag",
.c = 'b',
});
try testing.expectEqualSlices(u32, list.items(.a), &[_]u32{ 1, 2 });
try testing.expectEqualSlices(u8, list.items(.c), &[_]u8{ 'a', 'b' });
try testing.expectEqual(@as(usize, 2), list.items(.b).len);
try testing.expectEqualStrings("foobar", list.items(.b)[0]);
try testing.expectEqualStrings("zigzag", list.items(.b)[1]);
try list.append(ally, .{
.a = 3,
.b = "fizzbuzz",
.c = 'c',
});
try testing.expectEqualSlices(u32, list.items(.a), &[_]u32{ 1, 2, 3 });
try testing.expectEqualSlices(u8, list.items(.c), &[_]u8{ 'a', 'b', 'c' });
try testing.expectEqual(@as(usize, 3), list.items(.b).len);
try testing.expectEqualStrings("foobar", list.items(.b)[0]);
try testing.expectEqualStrings("zigzag", list.items(.b)[1]);
try testing.expectEqualStrings("fizzbuzz", list.items(.b)[2]);
var i: usize = 0;
while (i < 6) : (i += 1) {
try list.append(ally, .{
.a = @as(u32, @intCast(4 + i)),
.b = "whatever",
.c = @as(u8, @intCast('d' + i)),
});
}
try testing.expectEqualSlices(
u32,
&[_]u32{ 1, 2, 3, 4, 5, 6, 7, 8, 9 },
list.items(.a),
);
try testing.expectEqualSlices(
u8,
&[_]u8{ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i' },
list.items(.c),
);
list.shrinkAndFree(ally, 3);
try testing.expectEqualSlices(u32, list.items(.a), &[_]u32{ 1, 2, 3 });
try testing.expectEqualSlices(u8, list.items(.c), &[_]u8{ 'a', 'b', 'c' });
list.swap(0, 2);
try testing.expectEqualSlices(u32, list.items(.a), &[_]u32{ 3, 2, 1 });
try testing.expectEqualSlices(u8, list.items(.c), &[_]u8{ 'c', 'b', 'a' });
list.swap(2, 1);
try testing.expectEqualSlices(u32, list.items(.a), &[_]u32{ 3, 1, 2 });
try testing.expectEqualSlices(u8, list.items(.c), &[_]u8{ 'c', 'a', 'b' });
list.swap(2, 0);
try testing.expectEqualSlices(u32, list.items(.a), &[_]u32{ 2, 1, 3 });
try testing.expectEqualSlices(u8, list.items(.c), &[_]u8{ 'b', 'a', 'c' });
list.swap(0, 1);
try testing.expectEqualSlices(u32, list.items(.a), &[_]u32{ 1, 2, 3 });
try testing.expectEqualSlices(u8, list.items(.c), &[_]u8{ 'a', 'b', 'c' });
try testing.expectEqual(@as(usize, 3), list.items(.b).len);
try testing.expectEqualStrings("foobar", list.items(.b)[0]);
try testing.expectEqualStrings("zigzag", list.items(.b)[1]);
try testing.expectEqualStrings("fizzbuzz", list.items(.b)[2]);
try testing.expectError(error.OutOfMemory, list.addOneBounded());
list.set(try list.addOne(ally), .{
.a = 4,
.b = "xnopyt",
.c = 'd',
});
try testing.expectEqualStrings("xnopyt", list.pop().?.b);
try testing.expectEqual(@as(?u8, 'c'), if (list.pop()) |elem| elem.c else null);
try testing.expectEqual(@as(u32, 2), list.pop().?.a);
try testing.expectEqual(@as(u8, 'a'), list.pop().?.c);
try testing.expectEqual(@as(?Foo, null), list.pop());
list.clearRetainingCapacity();
try testing.expectEqual(0, list.len);
try testing.expect(list.capacity > 0);
list.clearAndFree(ally);
try testing.expectEqual(0, list.len);
try testing.expectEqual(0, list.capacity);
}
// function used the @reduce code path.
test "regression test for @reduce bug" {
const ally = testing.allocator;
var list: MultiArrayList(struct {
tag: std.zig.Token.Tag,
start: u32,
}) = .empty;
defer list.deinit(ally);
try list.ensureTotalCapacity(ally, 20);
try list.append(ally, .{ .tag = .keyword_const, .start = 0 });
try list.append(ally, .{ .tag = .identifier, .start = 6 });
try list.append(ally, .{ .tag = .equal, .start = 10 });
try list.append(ally, .{ .tag = .builtin, .start = 12 });
try list.append(ally, .{ .tag = .l_paren, .start = 19 });
try list.append(ally, .{ .tag = .string_literal, .start = 20 });
try list.append(ally, .{ .tag = .r_paren, .start = 25 });
try list.append(ally, .{ .tag = .semicolon, .start = 26 });
try list.append(ally, .{ .tag = .keyword_pub, .start = 29 });
try list.append(ally, .{ .tag = .keyword_fn, .start = 33 });
try list.append(ally, .{ .tag = .identifier, .start = 36 });
try list.append(ally, .{ .tag = .l_paren, .start = 40 });
try list.append(ally, .{ .tag = .r_paren, .start = 41 });
try list.append(ally, .{ .tag = .identifier, .start = 43 });
try list.append(ally, .{ .tag = .bang, .start = 51 });
try list.append(ally, .{ .tag = .identifier, .start = 52 });
try list.append(ally, .{ .tag = .l_brace, .start = 57 });
try list.append(ally, .{ .tag = .identifier, .start = 63 });
try list.append(ally, .{ .tag = .period, .start = 66 });
try list.append(ally, .{ .tag = .identifier, .start = 67 });
try list.append(ally, .{ .tag = .period, .start = 70 });
try list.append(ally, .{ .tag = .identifier, .start = 71 });
try list.append(ally, .{ .tag = .l_paren, .start = 75 });
try list.append(ally, .{ .tag = .string_literal, .start = 76 });
try list.append(ally, .{ .tag = .comma, .start = 113 });
try list.append(ally, .{ .tag = .period, .start = 115 });
try list.append(ally, .{ .tag = .l_brace, .start = 116 });
try list.append(ally, .{ .tag = .r_brace, .start = 117 });
try list.append(ally, .{ .tag = .r_paren, .start = 118 });
try list.append(ally, .{ .tag = .semicolon, .start = 119 });
try list.append(ally, .{ .tag = .r_brace, .start = 121 });
try list.append(ally, .{ .tag = .eof, .start = 123 });
const tags = list.items(.tag);
try testing.expectEqual(tags[1], .identifier);
try testing.expectEqual(tags[2], .equal);
try testing.expectEqual(tags[3], .builtin);
try testing.expectEqual(tags[4], .l_paren);
try testing.expectEqual(tags[5], .string_literal);
try testing.expectEqual(tags[6], .r_paren);
try testing.expectEqual(tags[7], .semicolon);
try testing.expectEqual(tags[8], .keyword_pub);
try testing.expectEqual(tags[9], .keyword_fn);
try testing.expectEqual(tags[10], .identifier);
try testing.expectEqual(tags[11], .l_paren);
try testing.expectEqual(tags[12], .r_paren);
try testing.expectEqual(tags[13], .identifier);
try testing.expectEqual(tags[14], .bang);
try testing.expectEqual(tags[15], .identifier);
try testing.expectEqual(tags[16], .l_brace);
try testing.expectEqual(tags[17], .identifier);
try testing.expectEqual(tags[18], .period);
try testing.expectEqual(tags[19], .identifier);
try testing.expectEqual(tags[20], .period);
try testing.expectEqual(tags[21], .identifier);
try testing.expectEqual(tags[22], .l_paren);
try testing.expectEqual(tags[23], .string_literal);
try testing.expectEqual(tags[24], .comma);
try testing.expectEqual(tags[25], .period);
try testing.expectEqual(tags[26], .l_brace);
try testing.expectEqual(tags[27], .r_brace);
try testing.expectEqual(tags[28], .r_paren);
try testing.expectEqual(tags[29], .semicolon);
try testing.expectEqual(tags[30], .r_brace);
try testing.expectEqual(tags[31], .eof);
}
test "ensure capacity on empty list" {
const ally = testing.allocator;
const Foo = struct {
a: u32,
b: u8,
};
var list: MultiArrayList(Foo) = .empty;
defer list.deinit(ally);
try list.ensureTotalCapacity(ally, 2);
list.appendAssumeCapacity(.{ .a = 1, .b = 2 });
list.appendAssumeCapacity(.{ .a = 3, .b = 4 });
try testing.expectEqualSlices(u32, &[_]u32{ 1, 3 }, list.items(.a));
try testing.expectEqualSlices(u8, &[_]u8{ 2, 4 }, list.items(.b));
list.len = 0;
list.appendAssumeCapacity(.{ .a = 5, .b = 6 });
list.appendAssumeCapacity(.{ .a = 7, .b = 8 });
try testing.expectEqualSlices(u32, &[_]u32{ 5, 7 }, list.items(.a));
try testing.expectEqualSlices(u8, &[_]u8{ 6, 8 }, list.items(.b));
list.len = 0;
try list.ensureTotalCapacity(ally, 16);
list.appendAssumeCapacity(.{ .a = 9, .b = 10 });
list.appendAssumeCapacity(.{ .a = 11, .b = 12 });
try testing.expectEqualSlices(u32, &[_]u32{ 9, 11 }, list.items(.a));
try testing.expectEqualSlices(u8, &[_]u8{ 10, 12 }, list.items(.b));
}
test "insert elements" {
const ally = testing.allocator;
const Foo = struct {
a: u8,
b: u32,
};
var list = try MultiArrayList(Foo).initCapacity(ally, 2);
defer list.deinit(ally);
try list.insertBounded(0, .{ .a = 1, .b = 2 });
list.insertAssumeCapacity(1, .{ .a = 2, .b = 3 });
try list.insert(ally, 0, .{ .a = 3, .b = 4 });
try testing.expectEqualSlices(u8, &[_]u8{ 3, 1, 2 }, list.items(.a));
try testing.expectEqualSlices(u32, &[_]u32{ 4, 2, 3 }, list.items(.b));
}
test "initCapacity" {
const gpa = testing.allocator;
var list = try MultiArrayList(struct { a: u8, b: u32 }).initCapacity(gpa, 404);
defer list.deinit(gpa);
try testing.expectEqual(0, list.len);
try testing.expectEqual(404, list.capacity);
}
test "union" {
const ally = testing.allocator;
const Foo = union(enum) {
a: u32,
b: []const u8,
};
var list: MultiArrayList(Foo) = .empty;
defer list.deinit(ally);
try testing.expectEqual(@as(usize, 0), list.items(.tags).len);
try list.ensureTotalCapacity(ally, 3);
list.appendAssumeCapacity(.{ .a = 1 });
list.appendAssumeCapacity(.{ .b = "zigzag" });
try testing.expectEqualSlices(meta.Tag(Foo), list.items(.tags), &.{ .a, .b });
try testing.expectEqual(@as(usize, 2), list.items(.tags).len);
list.appendAssumeCapacity(.{ .b = "foobar" });
try testing.expectEqualStrings("zigzag", list.items(.data)[1].b);
try testing.expectEqualStrings("foobar", list.items(.data)[2].b);
for (0..6) |i| {
try list.append(ally, .{ .a = @as(u32, @intCast(4 + i)) });
}
try testing.expectEqualSlices(
meta.Tag(Foo),
&.{ .a, .b, .b, .a, .a, .a, .a, .a, .a },
list.items(.tags),
);
try testing.expectEqual(Foo{ .a = 1 }, list.get(0));
try testing.expectEqual(Foo{ .b = "zigzag" }, list.get(1));
try testing.expectEqual(Foo{ .b = "foobar" }, list.get(2));
try testing.expectEqual(Foo{ .a = 4 }, list.get(3));
try testing.expectEqual(Foo{ .a = 5 }, list.get(4));
try testing.expectEqual(Foo{ .a = 6 }, list.get(5));
try testing.expectEqual(Foo{ .a = 7 }, list.get(6));
try testing.expectEqual(Foo{ .a = 8 }, list.get(7));
try testing.expectEqual(Foo{ .a = 9 }, list.get(8));
list.shrinkAndFree(ally, 3);
try testing.expectEqual(@as(usize, 3), list.items(.tags).len);
try testing.expectEqualSlices(meta.Tag(Foo), list.items(.tags), &.{ .a, .b, .b });
try testing.expectEqual(Foo{ .a = 1 }, list.get(0));
try testing.expectEqual(Foo{ .b = "zigzag" }, list.get(1));
try testing.expectEqual(Foo{ .b = "foobar" }, list.get(2));
}
test "sorting a span" {
var list: MultiArrayList(struct { score: u32, chr: u8 }) = .empty;
defer list.deinit(testing.allocator);
try list.ensureTotalCapacity(testing.allocator, 42);
for (
[42]u8{ 'b', 'a', 'c', 'a', 'b', 'c', 'b', 'c', 'b', 'a', 'b', 'a', 'b', 'c', 'b', 'a', 'a', 'c', 'c', 'a', 'c', 'b', 'a', 'c', 'a', 'b', 'b', 'c', 'c', 'b', 'a', 'b', 'a', 'b', 'c', 'b', 'a', 'a', 'c', 'c', 'a', 'c' },
[42]u32{ 1, 1, 1, 2, 2, 2, 3, 3, 4, 3, 5, 4, 6, 4, 7, 5, 6, 5, 6, 7, 7, 8, 8, 8, 9, 9, 10, 9, 10, 11, 10, 12, 11, 13, 11, 14, 12, 13, 12, 13, 14, 14 },
) |chr, score| {
list.appendAssumeCapacity(.{ .chr = chr, .score = score });
}
const sliced = list.slice();
list.sortSpan(6, 21, struct {
chars: []const u8,
fn lessThan(ctx: @This(), a: usize, b: usize) bool {
return ctx.chars[a] < ctx.chars[b];
}
}{ .chars = sliced.items(.chr) });
var i: u32 = undefined;
var j: u32 = 6;
var c: u8 = 'a';
while (j < 21) {
i = j;
j += 5;
var n: u32 = 3;
for (sliced.items(.chr)[i..j], sliced.items(.score)[i..j]) |chr, score| {
try testing.expectEqual(score, n);
try testing.expectEqual(chr, c);
n += 1;
}
c += 1;
}
}
test "0 sized struct field" {
const ally = testing.allocator;
const Foo = struct {
a: u0,
b: f32,
};
var list: MultiArrayList(Foo) = .empty;
defer list.deinit(ally);
try testing.expectEqualSlices(u0, &[_]u0{}, list.items(.a));
try testing.expectEqualSlices(f32, &[_]f32{}, list.items(.b));
try list.append(ally, .{ .a = 0, .b = 42.0 });
try testing.expectEqualSlices(u0, &[_]u0{0}, list.items(.a));
try testing.expectEqualSlices(f32, &[_]f32{42.0}, list.items(.b));
try list.insert(ally, 0, .{ .a = 0, .b = -1.0 });
try testing.expectEqualSlices(u0, &[_]u0{ 0, 0 }, list.items(.a));
try testing.expectEqualSlices(f32, &[_]f32{ -1.0, 42.0 }, list.items(.b));
list.swapRemove(list.len - 1);
try testing.expectEqualSlices(u0, &[_]u0{0}, list.items(.a));
try testing.expectEqualSlices(f32, &[_]f32{-1.0}, list.items(.b));
}
test "0 sized struct" {
const ally = testing.allocator;
const Foo = struct {
a: u0,
};
var list: MultiArrayList(Foo) = .empty;
defer list.deinit(ally);
try testing.expectEqualSlices(u0, &[_]u0{}, list.items(.a));
try list.append(ally, .{ .a = 0 });
try testing.expectEqualSlices(u0, &[_]u0{0}, list.items(.a));
try list.insert(ally, 0, .{ .a = 0 });
try testing.expectEqualSlices(u0, &[_]u0{ 0, 0 }, list.items(.a));
list.swapRemove(list.len - 1);
try testing.expectEqualSlices(u0, &[_]u0{0}, list.items(.a));
}
test "struct with many fields" {
const ManyFields = struct {
fn Type(count: comptime_int) type {
@setEvalBranchQuota(50000);
var field_names: [count][]const u8 = undefined;
for (&field_names, 0..) |*n, i| n.* = std.fmt.comptimePrint("a{d}", .{i});
return @Struct(.@"extern", null, &field_names, &@splat(u32), &@splat(.{}));
}
fn doTest(ally: std.mem.Allocator, count: comptime_int) !void {
var list: MultiArrayList(Type(count)) = .empty;
defer list.deinit(ally);
try list.resize(ally, 1);
list.items(.a0)[0] = 42;
}
};
try ManyFields.doTest(testing.allocator, 25);
try ManyFields.doTest(testing.allocator, 50);
try ManyFields.doTest(testing.allocator, 100);
try ManyFields.doTest(testing.allocator, 200);
}
test "orderedRemoveMany" {
const gpa = testing.allocator;
var list: MultiArrayList(struct { x: usize }) = .empty;
defer list.deinit(gpa);
for (0..10) |n| {
try list.append(gpa, .{ .x = n });
}
list.orderedRemoveMany(&.{ 1, 5, 5, 7, 9 });
try testing.expectEqualSlices(usize, &.{ 0, 2, 3, 4, 6, 8 }, list.items(.x));
list.orderedRemoveMany(&.{0});
try testing.expectEqualSlices(usize, &.{ 2, 3, 4, 6, 8 }, list.items(.x));
list.orderedRemoveMany(&.{});
try testing.expectEqualSlices(usize, &.{ 2, 3, 4, 6, 8 }, list.items(.x));
list.orderedRemoveMany(&.{ 1, 2, 3, 4 });
try testing.expectEqualSlices(usize, &.{2}, list.items(.x));
list.orderedRemoveMany(&.{0});
try testing.expectEqualSlices(usize, &.{}, list.items(.x));
}