Flash 237 lines
// list — the dynamic array, in pure Flash.
//
// `List(T)` mirrors Zig's unmanaged ArrayList surface — `.empty`, `deinit`,
// `append`, `appendSlice`, `toOwnedSlice`, `.items` — so a facade can
// re-route between the two implementations without touching a call site.
// The allocation is carried as a second slice (`buf`) rather than a raw
// capacity count, so the implementation needs nothing beyond the language's
// slice surface. Growth doubles the capacity (floor 4) and jumps straight
// to the requested length when a bulk append outruns the double.
use "base"
use "mem"
pub fn List(comptime T type) type {
return struct {
// The live elements, always a prefix of `buf`; the length is the
// list length. Index, slice, and write through freely.
items []mut T,
// The full allocation. `items.len <= buf.len` holds at all times.
buf []mut T,
const Self = #This()
// The canonical initializer: no allocation until the first append.
pub const empty Self = .{ .items = &.{}, .buf = &.{} }
// Release the allocation. The list is `.empty` afterwards and may
// be reused.
pub fn deinit(self *mut Self, alloc base.Allocator) void {
alloc.free(self.buf)
self.* = .empty
}
// Append one element, growing if needed.
pub fn append(self *mut Self, alloc base.Allocator, item T) !void {
try self.grow(alloc, self.items.len + 1)
n := self.items.len
self.buf[n] = item
self.items = self.buf[0 .. n + 1]
}
// Append every element of `source`, growing at most once.
pub fn appendSlice(self *mut Self, alloc base.Allocator, source []T) !void {
try self.grow(alloc, self.items.len + source.len)
n := self.items.len
mem.copy(T, self.buf[n .. n + source.len], source)
self.items = self.buf[0 .. n + source.len]
}
// Hand the elements to the caller as an exactly-sized allocation
// the caller owns. The list is `.empty` afterwards and may be
// reused.
pub fn toOwnedSlice(self *mut Self, alloc base.Allocator) ![]mut T {
owned := try alloc.alloc(T, self.items.len)
mem.copy(T, owned, self.items)
alloc.free(self.buf)
self.* = .empty
return owned
}
// Remove and return the element at `index` in O(1) by moving the
// last element into its place. Order is not preserved.
pub fn swapRemove(self *mut Self, index usize) T {
n := self.items.len
removed := self.items[index]
self.items[index] = self.items[n - 1]
self.items = self.buf[0 .. n - 1]
return removed
}
// Ensure the allocation holds at least `needed` elements. On
// failure the list is untouched — the old allocation is released
// only after the new one exists.
fn grow(self *mut Self, alloc base.Allocator, needed usize) !void {
if needed <= self.buf.len {
return
}
var cap usize = self.buf.len * 2
if cap < 4 {
cap = 4
}
if cap < needed {
cap = needed
}
grown := try alloc.alloc(T, cap)
mem.copy(T, grown, self.items)
alloc.free(self.buf)
self.buf = grown
self.items = grown[0..self.items.len]
}
}
}
const Pair = struct {
key i32,
tag u8,
}
test "append grows past the initial capacity" {
var xs List(i32) = .empty
defer xs.deinit(base.testAlloc)
var v i32 = 0
while v < 9 {
try xs.append(base.testAlloc, v)
v += 1
}
try base.expectEqual(9, xs.items.len)
try base.expectEqual(0, xs.items[0])
try base.expectEqual(8, xs.items[8])
try base.expect(xs.buf.len >= 9)
}
test "appendSlice appends in bulk" {
var s List(u8) = .empty
defer s.deinit(base.testAlloc)
try s.appendSlice(base.testAlloc, "flash")
try s.append(base.testAlloc, '!')
try base.expect(mem.eql(u8, s.items, "flash!"))
}
test "toOwnedSlice hands off the elements and resets the list" {
var s List(u8) = .empty
defer s.deinit(base.testAlloc)
try s.appendSlice(base.testAlloc, "ab")
owned := try s.toOwnedSlice(base.testAlloc)
defer base.testAlloc.free(owned)
try base.expect(mem.eql(u8, owned, "ab"))
try base.expectEqual(0, s.items.len)
try s.append(base.testAlloc, 'c')
try base.expectEqual('c', s.items[0])
}
test "toOwnedSlice on an empty list returns an empty slice" {
var s List(u8) = .empty
owned := try s.toOwnedSlice(base.testAlloc)
defer base.testAlloc.free(owned)
try base.expectEqual(0, owned.len)
}
test "swapRemove takes from the middle and the end" {
var xs List(u8) = .empty
defer xs.deinit(base.testAlloc)
try xs.appendSlice(base.testAlloc, "abcd")
try base.expectEqual('b', xs.swapRemove(1))
try base.expect(mem.eql(u8, xs.items, "adc"))
try base.expectEqual('c', xs.swapRemove(2))
try base.expect(mem.eql(u8, xs.items, "ad"))
try base.expectEqual('a', xs.swapRemove(0))
try base.expectEqual('d', xs.swapRemove(0))
try base.expectEqual(0, xs.items.len)
}
test "deinit releases and the list is reusable" {
var s List(u8) = .empty
try s.append(base.testAlloc, 'x')
s.deinit(base.testAlloc)
try base.expectEqual(0, s.items.len)
try s.append(base.testAlloc, 'y')
try base.expectEqual('y', s.items[0])
s.deinit(base.testAlloc)
}
// The failing-allocator sweep bodies. `checkAllAllocationFailures` re-runs
// each one, failing a different allocation per run: every induced OOM must
// propagate as an error with nothing leaked, and the final unfailed run
// must pass the assertions.
fn growthSweep(alloc base.Allocator) !void {
var xs List(i32) = .empty
defer xs.deinit(alloc)
var v i32 = 0
while v < 40 {
try xs.append(alloc, v)
v += 1
}
try base.expectEqual(40, xs.items.len)
}
fn appendSliceSweep(alloc base.Allocator) !void {
var s List(u8) = .empty
defer s.deinit(alloc)
try s.appendSlice(alloc, "ab")
try s.appendSlice(alloc, "0123456789abcdef")
try base.expect(mem.eql(u8, s.items, "ab0123456789abcdef"))
}
fn toOwnedSliceSweep(alloc base.Allocator) !void {
var s List(u8) = .empty
defer s.deinit(alloc)
try s.appendSlice(alloc, "flash")
owned := try s.toOwnedSlice(alloc)
defer alloc.free(owned)
try base.expect(mem.eql(u8, owned, "flash"))
}
test "append survives failure at every allocation point" {
try base.checkAllAllocationFailures(base.testAlloc, growthSweep, .{})
}
test "appendSlice survives failure at every allocation point" {
try base.checkAllAllocationFailures(base.testAlloc, appendSliceSweep, .{})
}
test "toOwnedSlice survives failure at every allocation point" {
try base.checkAllAllocationFailures(base.testAlloc, toOwnedSliceSweep, .{})
}
test "a failed grow leaves the list untouched" {
var failing = base.FailingAllocator.init(base.testAlloc, .{ .fail_index = 1 })
fa := failing.allocator()
var xs List(u8) = .empty
defer xs.deinit(base.testAlloc)
try xs.appendSlice(fa, "abcd")
try base.expectError(error.OutOfMemory, xs.append(fa, 'e'))
try base.expect(mem.eql(u8, xs.items, "abcd"))
try base.expectEqual(4, xs.buf.len)
try xs.append(base.testAlloc, 'e')
try base.expect(mem.eql(u8, xs.items, "abcde"))
}
test "struct and optional element types instantiate" {
var ps List(Pair) = .empty
defer ps.deinit(base.testAlloc)
try ps.append(base.testAlloc, .{ .key = 1, .tag = 'a' })
try ps.append(base.testAlloc, .{ .key = 2, .tag = 'b' })
try base.expectEqual(2, ps.items.len)
try base.expectEqual('b', ps.items[1].tag)
ps.items[0].key = 9
try base.expectEqual(9, ps.items[0].key)
var os List(?[]u8) = .empty
defer os.deinit(base.testAlloc)
try os.append(base.testAlloc, null)
try os.append(base.testAlloc, "s")
try base.expect(os.items[0] == null)
try base.expect(os.items[1] != null)
}