Commit e5aab62228

Veikka Tuominen <git@vexu.eu>
2020-12-23 15:24:22
move ArrayListSentineled to std lib orphanage
1 parent 51a9046
lib/std/array_list.zig
@@ -91,6 +91,13 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
             return result;
         }
 
+        /// The caller owns the returned memory. ArrayList becomes empty.
+        pub fn toOwnedSliceSentinel(self: *Self, comptime sentinel: T) ![:sentinel]T {
+            try self.append(sentinel);
+            const result = self.list.toOwnedSlice();
+            return result[0 .. result.len - 1 :sentinel];
+        }
+
         /// Insert `item` at index `n` by moving `list[n .. list.len]` to make room.
         /// This operation is O(N).
         pub fn insert(self: *Self, n: usize, item: T) !void {
@@ -389,6 +396,13 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
             return result;
         }
 
+        /// The caller owns the returned memory. ArrayList becomes empty.
+        pub fn toOwnedSliceSentinel(self: *Self, allocator: *Allocator, comptime sentinel: T) ![:sentinel]T {
+            try self.append(allocator, sentinel);
+            const result = self.list.toOwnedSlice(allocator);
+            return result[0 .. result.len - 1 :sentinel];
+        }
+
         /// Insert `item` at index `n`. Moves `list[n .. list.len]`
         /// to make room.
         pub fn insert(self: *Self, allocator: *Allocator, n: usize, item: T) !void {
lib/std/array_list_sentineled.zig
@@ -1,229 +0,0 @@
-// SPDX-License-Identifier: MIT
-// Copyright (c) 2015-2020 Zig Contributors
-// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
-// The MIT license requires this copyright notice to be included in all copies
-// and substantial portions of the software.
-const std = @import("std.zig");
-const debug = std.debug;
-const mem = std.mem;
-const Allocator = mem.Allocator;
-const assert = debug.assert;
-const testing = std.testing;
-const ArrayList = std.ArrayList;
-
-/// A contiguous, growable list of items in memory, with a sentinel after them.
-/// The sentinel is maintained when appending, resizing, etc.
-/// If you do not need a sentinel, consider using `ArrayList` instead.
-pub fn ArrayListSentineled(comptime T: type, comptime sentinel: T) type {
-    return struct {
-        list: ArrayList(T),
-
-        const Self = @This();
-
-        /// Must deinitialize with deinit.
-        pub fn init(allocator: *Allocator, m: []const T) !Self {
-            var self = try initSize(allocator, m.len);
-            mem.copy(T, self.list.items, m);
-            return self;
-        }
-
-        /// Initialize memory to size bytes of undefined values.
-        /// Must deinitialize with deinit.
-        pub fn initSize(allocator: *Allocator, size: usize) !Self {
-            var self = initNull(allocator);
-            try self.resize(size);
-            return self;
-        }
-
-        /// Initialize with capacity to hold at least num bytes.
-        /// Must deinitialize with deinit.
-        pub fn initCapacity(allocator: *Allocator, num: usize) !Self {
-            var self = Self{ .list = try ArrayList(T).initCapacity(allocator, num + 1) };
-            self.list.appendAssumeCapacity(sentinel);
-            return self;
-        }
-
-        /// Must deinitialize with deinit.
-        /// None of the other operations are valid until you do one of these:
-        /// * `replaceContents`
-        /// * `resize`
-        pub fn initNull(allocator: *Allocator) Self {
-            return Self{ .list = ArrayList(T).init(allocator) };
-        }
-
-        /// Must deinitialize with deinit.
-        pub fn initFromBuffer(buffer: Self) !Self {
-            return Self.init(buffer.list.allocator, buffer.span());
-        }
-
-        /// Takes ownership of the passed in slice. The slice must have been
-        /// allocated with `allocator`.
-        /// Must deinitialize with deinit.
-        pub fn fromOwnedSlice(allocator: *Allocator, slice: []T) !Self {
-            var self = Self{ .list = ArrayList(T).fromOwnedSlice(allocator, slice) };
-            try self.list.append(sentinel);
-            return self;
-        }
-
-        /// The caller owns the returned memory. The list becomes null and is safe to `deinit`.
-        pub fn toOwnedSlice(self: *Self) [:sentinel]T {
-            const allocator = self.list.allocator;
-            const result = self.list.toOwnedSlice();
-            self.* = initNull(allocator);
-            return result[0 .. result.len - 1 :sentinel];
-        }
-
-        /// Only works when `T` is `u8`.
-        pub fn allocPrint(allocator: *Allocator, comptime format: []const u8, args: anytype) !Self {
-            const size = std.math.cast(usize, std.fmt.count(format, args)) catch |err| switch (err) {
-                error.Overflow => return error.OutOfMemory,
-            };
-            var self = try Self.initSize(allocator, size);
-            assert((std.fmt.bufPrint(self.list.items, format, args) catch unreachable).len == size);
-            return self;
-        }
-
-        pub fn deinit(self: *Self) void {
-            self.list.deinit();
-        }
-
-        pub fn span(self: anytype) @TypeOf(self.list.items[0..:sentinel]) {
-            return self.list.items[0..self.len() :sentinel];
-        }
-
-        pub fn shrink(self: *Self, new_len: usize) void {
-            assert(new_len <= self.len());
-            self.list.shrink(new_len + 1);
-            self.list.items[self.len()] = sentinel;
-        }
-
-        pub fn resize(self: *Self, new_len: usize) !void {
-            try self.list.resize(new_len + 1);
-            self.list.items[self.len()] = sentinel;
-        }
-
-        pub fn isNull(self: Self) bool {
-            return self.list.items.len == 0;
-        }
-
-        pub fn len(self: Self) usize {
-            return self.list.items.len - 1;
-        }
-
-        pub fn capacity(self: Self) usize {
-            return if (self.list.capacity > 0)
-                self.list.capacity - 1
-            else
-                0;
-        }
-
-        pub fn appendSlice(self: *Self, m: []const T) !void {
-            const old_len = self.len();
-            try self.resize(old_len + m.len);
-            mem.copy(T, self.list.items[old_len..], m);
-        }
-
-        pub fn append(self: *Self, byte: T) !void {
-            const old_len = self.len();
-            try self.resize(old_len + 1);
-            self.list.items[old_len] = byte;
-        }
-
-        pub fn eql(self: Self, m: []const T) bool {
-            return mem.eql(T, self.span(), m);
-        }
-
-        pub fn startsWith(self: Self, m: []const T) bool {
-            if (self.len() < m.len) return false;
-            return mem.eql(T, self.list.items[0..m.len], m);
-        }
-
-        pub fn endsWith(self: Self, m: []const T) bool {
-            const l = self.len();
-            if (l < m.len) return false;
-            const start = l - m.len;
-            return mem.eql(T, self.list.items[start..l], m);
-        }
-
-        pub fn replaceContents(self: *Self, m: []const T) !void {
-            try self.resize(m.len);
-            mem.copy(T, self.list.items, m);
-        }
-
-        /// Initializes an OutStream which will append to the list.
-        /// This function may be called only when `T` is `u8`.
-        pub fn outStream(self: *Self) std.io.OutStream(*Self, error{OutOfMemory}, appendWrite) {
-            return .{ .context = self };
-        }
-
-        /// Same as `append` except it returns the number of bytes written, which is always the same
-        /// as `m.len`. The purpose of this function existing is to match `std.io.OutStream` API.
-        /// This function may be called only when `T` is `u8`.
-        pub fn appendWrite(self: *Self, m: []const u8) !usize {
-            try self.appendSlice(m);
-            return m.len;
-        }
-    };
-}
-
-test "simple" {
-    var buf = try ArrayListSentineled(u8, 0).init(testing.allocator, "");
-    defer buf.deinit();
-
-    testing.expect(buf.len() == 0);
-    try buf.appendSlice("hello");
-    try buf.appendSlice(" ");
-    try buf.appendSlice("world");
-    testing.expect(buf.eql("hello world"));
-    testing.expect(mem.eql(u8, mem.spanZ(buf.span().ptr), buf.span()));
-
-    var buf2 = try ArrayListSentineled(u8, 0).initFromBuffer(buf);
-    defer buf2.deinit();
-    testing.expect(buf.eql(buf2.span()));
-
-    testing.expect(buf.startsWith("hell"));
-    testing.expect(buf.endsWith("orld"));
-
-    try buf2.resize(4);
-    testing.expect(buf.startsWith(buf2.span()));
-}
-
-test "initSize" {
-    var buf = try ArrayListSentineled(u8, 0).initSize(testing.allocator, 3);
-    defer buf.deinit();
-    testing.expect(buf.len() == 3);
-    try buf.appendSlice("hello");
-    testing.expect(mem.eql(u8, buf.span()[3..], "hello"));
-}
-
-test "initCapacity" {
-    var buf = try ArrayListSentineled(u8, 0).initCapacity(testing.allocator, 10);
-    defer buf.deinit();
-    testing.expect(buf.len() == 0);
-    testing.expect(buf.capacity() >= 10);
-    const old_cap = buf.capacity();
-    try buf.appendSlice("hello");
-    testing.expect(buf.len() == 5);
-    testing.expect(buf.capacity() == old_cap);
-    testing.expect(mem.eql(u8, buf.span(), "hello"));
-}
-
-test "print" {
-    var buf = try ArrayListSentineled(u8, 0).init(testing.allocator, "");
-    defer buf.deinit();
-
-    try buf.outStream().print("Hello {} the {}", .{ 2, "world" });
-    testing.expect(buf.eql("Hello 2 the world"));
-}
-
-test "outStream" {
-    var buffer = try ArrayListSentineled(u8, 0).initSize(testing.allocator, 0);
-    defer buffer.deinit();
-    const buf_stream = buffer.outStream();
-
-    const x: i32 = 42;
-    const y: i32 = 1234;
-    try buf_stream.print("x: {}\ny: {}\n", .{ x, y });
-
-    testing.expect(mem.eql(u8, buffer.span(), "x: 42\ny: 1234\n"));
-}
lib/std/child_process.zig
@@ -15,7 +15,6 @@ const windows = os.windows;
 const mem = std.mem;
 const debug = std.debug;
 const BufMap = std.BufMap;
-const ArrayListSentineled = std.ArrayListSentineled;
 const builtin = @import("builtin");
 const Os = builtin.Os;
 const TailQueue = std.TailQueue;
@@ -749,38 +748,38 @@ fn windowsCreateProcess(app_name: [*:0]u16, cmd_line: [*:0]u16, envp_ptr: ?[*]u1
 
 /// Caller must dealloc.
 fn windowsCreateCommandLine(allocator: *mem.Allocator, argv: []const []const u8) ![:0]u8 {
-    var buf = try ArrayListSentineled(u8, 0).initSize(allocator, 0);
+    var buf = try ArrayList(u8).init(allocator);
     defer buf.deinit();
-    const buf_stream = buf.outStream();
+    const buf_wi = buf.outStream();
 
     for (argv) |arg, arg_i| {
-        if (arg_i != 0) try buf_stream.writeByte(' ');
+        if (arg_i != 0) try buf.append(' ');
         if (mem.indexOfAny(u8, arg, " \t\n\"") == null) {
-            try buf_stream.writeAll(arg);
+            try buf.appendSlice(arg);
             continue;
         }
-        try buf_stream.writeByte('"');
+        try buf.append('"');
         var backslash_count: usize = 0;
         for (arg) |byte| {
             switch (byte) {
                 '\\' => backslash_count += 1,
                 '"' => {
-                    try buf_stream.writeByteNTimes('\\', backslash_count * 2 + 1);
-                    try buf_stream.writeByte('"');
+                    try buf.appendNTimes('\\', backslash_count * 2 + 1);
+                    try buf.append('"');
                     backslash_count = 0;
                 },
                 else => {
-                    try buf_stream.writeByteNTimes('\\', backslash_count);
-                    try buf_stream.writeByte(byte);
+                    try buf.appendNTimes('\\', backslash_count);
+                    try buf.append(byte);
                     backslash_count = 0;
                 },
             }
         }
-        try buf_stream.writeByteNTimes('\\', backslash_count * 2);
-        try buf_stream.writeByte('"');
+        try buf.appendNTimes('\\', backslash_count * 2);
+        try buf.append('"');
     }
 
-    return buf.toOwnedSlice();
+    return buf.toOwnedSliceSentinel(0);
 }
 
 fn windowsDestroyPipe(rd: ?windows.HANDLE, wr: ?windows.HANDLE) void {
lib/std/net.zig
@@ -783,13 +783,13 @@ pub fn getAddressList(allocator: *mem.Allocator, name: []const u8, port: u16) !*
         var lookup_addrs = std.ArrayList(LookupAddr).init(allocator);
         defer lookup_addrs.deinit();
 
-        var canon = std.ArrayListSentineled(u8, 0).initNull(arena);
+        var canon = std.ArrayList(u8).init(arena);
         defer canon.deinit();
 
         try linuxLookupName(&lookup_addrs, &canon, name, family, flags, port);
 
         result.addrs = try arena.alloc(Address, lookup_addrs.items.len);
-        if (!canon.isNull()) {
+        if (canon.items.len != 0) {
             result.canon_name = canon.toOwnedSlice();
         }
 
@@ -818,7 +818,7 @@ const DAS_ORDER_SHIFT = 0;
 
 fn linuxLookupName(
     addrs: *std.ArrayList(LookupAddr),
-    canon: *std.ArrayListSentineled(u8, 0),
+    canon: *std.ArrayList(u8),
     opt_name: ?[]const u8,
     family: os.sa_family_t,
     flags: u32,
@@ -826,7 +826,8 @@ fn linuxLookupName(
 ) !void {
     if (opt_name) |name| {
         // reject empty name and check len so it fits into temp bufs
-        try canon.replaceContents(name);
+        canon.items.len = 0;
+        try canon.appendSlice(name);
         if (Address.parseExpectingFamily(name, family, port)) |addr| {
             try addrs.append(LookupAddr{ .addr = addr });
         } else |name_err| if ((flags & std.c.AI_NUMERICHOST) != 0) {
@@ -1091,7 +1092,7 @@ fn linuxLookupNameFromNull(
 
 fn linuxLookupNameFromHosts(
     addrs: *std.ArrayList(LookupAddr),
-    canon: *std.ArrayListSentineled(u8, 0),
+    canon: *std.ArrayList(u8),
     name: []const u8,
     family: os.sa_family_t,
     port: u16,
@@ -1142,7 +1143,8 @@ fn linuxLookupNameFromHosts(
         // first name is canonical name
         const name_text = first_name_text.?;
         if (isValidHostName(name_text)) {
-            try canon.replaceContents(name_text);
+            canon.items.len = 0;
+            try canon.appendSlice(name_text);
         }
     }
 }
@@ -1161,7 +1163,7 @@ pub fn isValidHostName(hostname: []const u8) bool {
 
 fn linuxLookupNameFromDnsSearch(
     addrs: *std.ArrayList(LookupAddr),
-    canon: *std.ArrayListSentineled(u8, 0),
+    canon: *std.ArrayList(u8),
     name: []const u8,
     family: os.sa_family_t,
     port: u16,
@@ -1177,10 +1179,10 @@ fn linuxLookupNameFromDnsSearch(
         if (byte == '.') dots += 1;
     }
 
-    const search = if (rc.search.isNull() or dots >= rc.ndots or mem.endsWith(u8, name, "."))
+    const search = if (dots >= rc.ndots or mem.endsWith(u8, name, "."))
         ""
     else
-        rc.search.span();
+        rc.search.items;
 
     var canon_name = name;
 
@@ -1193,14 +1195,14 @@ fn linuxLookupNameFromDnsSearch(
     // name is not a CNAME record) and serves as a buffer for passing
     // the full requested name to name_from_dns.
     try canon.resize(canon_name.len);
-    mem.copy(u8, canon.span(), canon_name);
+    mem.copy(u8, canon.items, canon_name);
     try canon.append('.');
 
     var tok_it = mem.tokenize(search, " \t");
     while (tok_it.next()) |tok| {
         canon.shrink(canon_name.len + 1);
         try canon.appendSlice(tok);
-        try linuxLookupNameFromDns(addrs, canon, canon.span(), family, rc, port);
+        try linuxLookupNameFromDns(addrs, canon, canon.items, family, rc, port);
         if (addrs.items.len != 0) return;
     }
 
@@ -1210,13 +1212,13 @@ fn linuxLookupNameFromDnsSearch(
 
 const dpc_ctx = struct {
     addrs: *std.ArrayList(LookupAddr),
-    canon: *std.ArrayListSentineled(u8, 0),
+    canon: *std.ArrayList(u8),
     port: u16,
 };
 
 fn linuxLookupNameFromDns(
     addrs: *std.ArrayList(LookupAddr),
-    canon: *std.ArrayListSentineled(u8, 0),
+    canon: *std.ArrayList(u8),
     name: []const u8,
     family: os.sa_family_t,
     rc: ResolvConf,
@@ -1271,7 +1273,7 @@ const ResolvConf = struct {
     attempts: u32,
     ndots: u32,
     timeout: u32,
-    search: std.ArrayListSentineled(u8, 0),
+    search: std.ArrayList(u8),
     ns: std.ArrayList(LookupAddr),
 
     fn deinit(rc: *ResolvConf) void {
@@ -1286,7 +1288,7 @@ const ResolvConf = struct {
 fn getResolvConf(allocator: *mem.Allocator, rc: *ResolvConf) !void {
     rc.* = ResolvConf{
         .ns = std.ArrayList(LookupAddr).init(allocator),
-        .search = std.ArrayListSentineled(u8, 0).initNull(allocator),
+        .search = std.ArrayList(u8).init(allocator),
         .ndots = 1,
         .timeout = 5,
         .attempts = 2,
@@ -1338,7 +1340,8 @@ fn getResolvConf(allocator: *mem.Allocator, rc: *ResolvConf) !void {
             const ip_txt = line_it.next() orelse continue;
             try linuxLookupNameFromNumericUnspec(&rc.ns, ip_txt, 53);
         } else if (mem.eql(u8, token, "domain") or mem.eql(u8, token, "search")) {
-            try rc.search.replaceContents(line_it.rest());
+            rc.search.items.len = 0;
+            try rc.search.appendSlice(line_it.rest());
         }
     }
 
@@ -1569,7 +1572,8 @@ fn dnsParseCallback(ctx: dpc_ctx, rr: u8, data: []const u8, packet: []const u8)
             _ = try os.dn_expand(packet, data, &tmp);
             const canon_name = mem.spanZ(std.meta.assumeSentinel(&tmp, 0));
             if (isValidHostName(canon_name)) {
-                try ctx.canon.replaceContents(canon_name);
+                ctx.canon.items.len = 0;
+                try ctx.canon.appendSlice(canon_name);
             }
         },
         else => return,
lib/std/std.zig
@@ -8,7 +8,6 @@ pub const ArrayHashMapUnmanaged = array_hash_map.ArrayHashMapUnmanaged;
 pub const ArrayList = @import("array_list.zig").ArrayList;
 pub const ArrayListAligned = @import("array_list.zig").ArrayListAligned;
 pub const ArrayListAlignedUnmanaged = @import("array_list.zig").ArrayListAlignedUnmanaged;
-pub const ArrayListSentineled = @import("array_list_sentineled.zig").ArrayListSentineled;
 pub const ArrayListUnmanaged = @import("array_list.zig").ArrayListUnmanaged;
 pub const AutoArrayHashMap = array_hash_map.AutoArrayHashMap;
 pub const AutoArrayHashMapUnmanaged = array_hash_map.AutoArrayHashMapUnmanaged;
src/DepTokenizer.zig
@@ -885,7 +885,7 @@ fn depTokenizer(input: []const u8, expect: []const u8) !void {
     defer arena_allocator.deinit();
 
     var it: Tokenizer = .{ .bytes = input };
-    var buffer = try std.ArrayListSentineled(u8, 0).initSize(arena, 0);
+    var buffer = std.ArrayList(u8).init(arena);
     var resolve_buf = std.ArrayList(u8).init(arena);
     var i: usize = 0;
     while (it.next()) |token| {
@@ -916,9 +916,8 @@ fn depTokenizer(input: []const u8, expect: []const u8) !void {
         }
         i += 1;
     }
-    const got: []const u8 = buffer.span();
 
-    if (std.mem.eql(u8, expect, got)) {
+    if (std.mem.eql(u8, expect, buffer.items)) {
         testing.expect(true);
         return;
     }
test/standalone/brace_expansion/main.zig
@@ -4,7 +4,6 @@ const mem = std.mem;
 const debug = std.debug;
 const assert = debug.assert;
 const testing = std.testing;
-const ArrayListSentineled = std.ArrayListSentineled;
 const ArrayList = std.ArrayList;
 const maxInt = std.math.maxInt;
 
@@ -16,7 +15,8 @@ const Token = union(enum) {
     Eof,
 };
 
-var global_allocator: *mem.Allocator = undefined;
+var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+var global_allocator = &gpa.allocator;
 
 fn tokenize(input: []const u8) !ArrayList(Token) {
     const State = enum {
@@ -25,12 +25,13 @@ fn tokenize(input: []const u8) !ArrayList(Token) {
     };
 
     var token_list = ArrayList(Token).init(global_allocator);
+    errdefer token_list.deinit();
     var tok_begin: usize = undefined;
     var state = State.Start;
 
     for (input) |b, i| {
         switch (state) {
-            State.Start => switch (b) {
+            .Start => switch (b) {
                 'a'...'z', 'A'...'Z' => {
                     state = State.Word;
                     tok_begin = i;
@@ -40,7 +41,7 @@ fn tokenize(input: []const u8) !ArrayList(Token) {
                 ',' => try token_list.append(Token.Comma),
                 else => return error.InvalidInput,
             },
-            State.Word => switch (b) {
+            .Word => switch (b) {
                 'a'...'z', 'A'...'Z' => {},
                 '{', '}', ',' => {
                     try token_list.append(Token{ .Word = input[tok_begin..i] });
@@ -68,6 +69,23 @@ const Node = union(enum) {
     Scalar: []const u8,
     List: ArrayList(Node),
     Combine: []Node,
+
+    fn deinit(self: Node) void {
+        switch (self) {
+            .Scalar => {},
+            .Combine => |pair| {
+                pair[0].deinit();
+                pair[1].deinit();
+                global_allocator.free(pair);
+            },
+            .List => |list| {
+                for (list.items) |item| {
+                    item.deinit();
+                }
+                list.deinit();
+            },
+        }
+    }
 };
 
 const ParseError = error{
@@ -80,9 +98,13 @@ fn parse(tokens: *const ArrayList(Token), token_index: *usize) ParseError!Node {
     token_index.* += 1;
 
     const result_node = switch (first_token) {
-        Token.Word => |word| Node{ .Scalar = word },
-        Token.OpenBrace => blk: {
+        .Word => |word| Node{ .Scalar = word },
+        .OpenBrace => blk: {
             var list = ArrayList(Node).init(global_allocator);
+            errdefer {
+                for (list.items) |node| node.deinit();
+                list.deinit();
+            }
             while (true) {
                 try list.append(try parse(tokens, token_index));
 
@@ -90,8 +112,8 @@ fn parse(tokens: *const ArrayList(Token), token_index: *usize) ParseError!Node {
                 token_index.* += 1;
 
                 switch (token) {
-                    Token.CloseBrace => break,
-                    Token.Comma => continue,
+                    .CloseBrace => break,
+                    .Comma => continue,
                     else => return error.InvalidInput,
                 }
             }
@@ -101,8 +123,9 @@ fn parse(tokens: *const ArrayList(Token), token_index: *usize) ParseError!Node {
     };
 
     switch (tokens.items[token_index.*]) {
-        Token.Word, Token.OpenBrace => {
+        .Word, .OpenBrace => {
             const pair = try global_allocator.alloc(Node, 2);
+            errdefer global_allocator.free(pair);
             pair[0] = result_node;
             pair[1] = try parse(tokens, token_index);
             return Node{ .Combine = pair };
@@ -111,22 +134,27 @@ fn parse(tokens: *const ArrayList(Token), token_index: *usize) ParseError!Node {
     }
 }
 
-fn expandString(input: []const u8, output: *ArrayListSentineled(u8, 0)) !void {
+fn expandString(input: []const u8, output: *ArrayList(u8)) !void {
     const tokens = try tokenize(input);
+    defer tokens.deinit();
     if (tokens.items.len == 1) {
         return output.resize(0);
     }
 
     var token_index: usize = 0;
     const root = try parse(&tokens, &token_index);
+    defer root.deinit();
     const last_token = tokens.items[token_index];
     switch (last_token) {
         Token.Eof => {},
         else => return error.InvalidInput,
     }
 
-    var result_list = ArrayList(ArrayListSentineled(u8, 0)).init(global_allocator);
-    defer result_list.deinit();
+    var result_list = ArrayList(ArrayList(u8)).init(global_allocator);
+    defer {
+        for (result_list.items) |*buf| buf.deinit();
+        result_list.deinit();
+    }
 
     try expandNode(root, &result_list);
 
@@ -135,39 +163,56 @@ fn expandString(input: []const u8, output: *ArrayListSentineled(u8, 0)) !void {
         if (i != 0) {
             try output.append(' ');
         }
-        try output.appendSlice(buf.span());
+        try output.appendSlice(buf.items);
     }
 }
 
 const ExpandNodeError = error{OutOfMemory};
 
-fn expandNode(node: Node, output: *ArrayList(ArrayListSentineled(u8, 0))) ExpandNodeError!void {
+fn expandNode(node: Node, output: *ArrayList(ArrayList(u8))) ExpandNodeError!void {
     assert(output.items.len == 0);
     switch (node) {
-        Node.Scalar => |scalar| {
-            try output.append(try ArrayListSentineled(u8, 0).init(global_allocator, scalar));
+        .Scalar => |scalar| {
+            var list = ArrayList(u8).init(global_allocator);
+            errdefer list.deinit();
+            try list.appendSlice(scalar);
+            try output.append(list);
         },
-        Node.Combine => |pair| {
+        .Combine => |pair| {
             const a_node = pair[0];
             const b_node = pair[1];
 
-            var child_list_a = ArrayList(ArrayListSentineled(u8, 0)).init(global_allocator);
+            var child_list_a = ArrayList(ArrayList(u8)).init(global_allocator);
+            defer {
+                for (child_list_a.items) |*buf| buf.deinit();
+                child_list_a.deinit();
+            }
             try expandNode(a_node, &child_list_a);
 
-            var child_list_b = ArrayList(ArrayListSentineled(u8, 0)).init(global_allocator);
+            var child_list_b = ArrayList(ArrayList(u8)).init(global_allocator);
+            defer {
+                for (child_list_b.items) |*buf| buf.deinit();
+                child_list_b.deinit();
+            }
             try expandNode(b_node, &child_list_b);
 
             for (child_list_a.items) |buf_a| {
                 for (child_list_b.items) |buf_b| {
-                    var combined_buf = try ArrayListSentineled(u8, 0).initFromBuffer(buf_a);
-                    try combined_buf.appendSlice(buf_b.span());
+                    var combined_buf = ArrayList(u8).init(global_allocator);
+                    errdefer combined_buf.deinit();
+
+                    try combined_buf.appendSlice(buf_a.items);
+                    try combined_buf.appendSlice(buf_b.items);
                     try output.append(combined_buf);
                 }
             }
         },
-        Node.List => |list| {
+        .List => |list| {
             for (list.items) |child_node| {
-                var child_list = ArrayList(ArrayListSentineled(u8, 0)).init(global_allocator);
+                var child_list = ArrayList(ArrayList(u8)).init(global_allocator);
+                errdefer for (child_list.items) |*buf| buf.deinit();
+                defer child_list.deinit();
+
                 try expandNode(child_node, &child_list);
 
                 for (child_list.items) |buf| {
@@ -179,32 +224,22 @@ fn expandNode(node: Node, output: *ArrayList(ArrayListSentineled(u8, 0))) Expand
 }
 
 pub fn main() !void {
+    defer _ = gpa.deinit();
     const stdin_file = io.getStdIn();
     const stdout_file = io.getStdOut();
 
-    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
-    defer arena.deinit();
-
-    global_allocator = &arena.allocator;
+    const stdin = try stdin_file.reader().readAllAlloc(global_allocator, std.math.maxInt(usize));
+    defer global_allocator.free(stdin);
 
-    var stdin_buf = try ArrayListSentineled(u8, 0).initSize(global_allocator, 0);
-    defer stdin_buf.deinit();
-
-    var stdin_adapter = stdin_file.inStream();
-    try stdin_adapter.stream.readAllBuffer(&stdin_buf, maxInt(usize));
-
-    var result_buf = try ArrayListSentineled(u8, 0).initSize(global_allocator, 0);
+    var result_buf = ArrayList(u8).init(global_allocator);
     defer result_buf.deinit();
 
-    try expandString(stdin_buf.span(), &result_buf);
-    try stdout_file.write(result_buf.span());
+    try expandString(stdin_buf.items, &result_buf);
+    try stdout_file.write(result_buf.items);
 }
 
 test "invalid inputs" {
-    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
-    defer arena.deinit();
-
-    global_allocator = &arena.allocator;
+    global_allocator = std.testing.allocator;
 
     expectError("}ABC", error.InvalidInput);
     expectError("{ABC", error.InvalidInput);
@@ -218,17 +253,14 @@ test "invalid inputs" {
 }
 
 fn expectError(test_input: []const u8, expected_err: anyerror) void {
-    var output_buf = ArrayListSentineled(u8, 0).initSize(global_allocator, 0) catch unreachable;
+    var output_buf = ArrayList(u8).init(global_allocator);
     defer output_buf.deinit();
 
     testing.expectError(expected_err, expandString(test_input, &output_buf));
 }
 
 test "valid inputs" {
-    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
-    defer arena.deinit();
-
-    global_allocator = &arena.allocator;
+    global_allocator = std.testing.allocator;
 
     expectExpansion("{x,y,z}", "x y z");
     expectExpansion("{A,B}{x,y}", "Ax Ay Bx By");
@@ -251,10 +283,10 @@ test "valid inputs" {
 }
 
 fn expectExpansion(test_input: []const u8, expected_result: []const u8) void {
-    var result = ArrayListSentineled(u8, 0).initSize(global_allocator, 0) catch unreachable;
+    var result = ArrayList(u8).init(global_allocator);
     defer result.deinit();
 
     expandString(test_input, &result) catch unreachable;
 
-    testing.expectEqualSlices(u8, expected_result, result.span());
+    testing.expectEqualSlices(u8, expected_result, result.items);
 }