|
| 1 | +const std = @import("std.zig"); |
| 2 | +const debug = std.debug; |
| 3 | +const mem = std.mem; |
| 4 | +const Allocator = mem.Allocator; |
| 5 | +const assert = debug.assert; |
| 6 | +const testing = std.testing; |
| 7 | +const ArrayList = std.ArrayList; |
| 8 | + |
| 9 | +/// A contiguous, growable list of items in memory, with a sentinel after them. |
| 10 | +/// The sentinel is maintained when appending, resizing, etc. |
| 11 | +/// If you do not need a sentinel, consider using `ArrayList` instead. |
| 12 | +pub fn ArrayListSentineled(comptime T: type, comptime sentinel: T) type { |
| 13 | + return struct { |
| 14 | + list: ArrayList(T), |
| 15 | + |
| 16 | + const Self = @This(); |
| 17 | + |
| 18 | + /// Must deinitialize with deinit. |
| 19 | + pub fn init(allocator: *Allocator, m: []const T) !Self { |
| 20 | + var self = try initSize(allocator, m.len); |
| 21 | + mem.copy(T, self.list.items, m); |
| 22 | + return self; |
| 23 | + } |
| 24 | + |
| 25 | + /// Initialize memory to size bytes of undefined values. |
| 26 | + /// Must deinitialize with deinit. |
| 27 | + pub fn initSize(allocator: *Allocator, size: usize) !Self { |
| 28 | + var self = initNull(allocator); |
| 29 | + try self.resize(size); |
| 30 | + return self; |
| 31 | + } |
| 32 | + |
| 33 | + /// Initialize with capacity to hold at least num bytes. |
| 34 | + /// Must deinitialize with deinit. |
| 35 | + pub fn initCapacity(allocator: *Allocator, num: usize) !Self { |
| 36 | + var self = Self{ .list = try ArrayList(T).initCapacity(allocator, num + 1) }; |
| 37 | + self.list.appendAssumeCapacity(sentinel); |
| 38 | + return self; |
| 39 | + } |
| 40 | + |
| 41 | + /// Must deinitialize with deinit. |
| 42 | + /// None of the other operations are valid until you do one of these: |
| 43 | + /// * `replaceContents` |
| 44 | + /// * `resize` |
| 45 | + pub fn initNull(allocator: *Allocator) Self { |
| 46 | + return Self{ .list = ArrayList(T).init(allocator) }; |
| 47 | + } |
| 48 | + |
| 49 | + /// Must deinitialize with deinit. |
| 50 | + pub fn initFromBuffer(buffer: Self) !Self { |
| 51 | + return Self.init(buffer.list.allocator, buffer.span()); |
| 52 | + } |
| 53 | + |
| 54 | + /// Takes ownership of the passed in slice. The slice must have been |
| 55 | + /// allocated with `allocator`. |
| 56 | + /// Must deinitialize with deinit. |
| 57 | + pub fn fromOwnedSlice(allocator: *Allocator, slice: []T) !Self { |
| 58 | + var self = Self{ .list = ArrayList(T).fromOwnedSlice(allocator, slice) }; |
| 59 | + try self.list.append(sentinel); |
| 60 | + return self; |
| 61 | + } |
| 62 | + |
| 63 | + /// The caller owns the returned memory. The list becomes null and is safe to `deinit`. |
| 64 | + pub fn toOwnedSlice(self: *Self) [:sentinel]T { |
| 65 | + const allocator = self.list.allocator; |
| 66 | + const result = self.list.toOwnedSlice(); |
| 67 | + self.* = initNull(allocator); |
| 68 | + return result[0 .. result.len - 1 :sentinel]; |
| 69 | + } |
| 70 | + |
| 71 | + /// Only works when `T` is `u8`. |
| 72 | + pub fn allocPrint(allocator: *Allocator, comptime format: []const u8, args: var) !Self { |
| 73 | + const size = std.math.cast(usize, std.fmt.count(format, args)) catch |err| switch (err) { |
| 74 | + error.Overflow => return error.OutOfMemory, |
| 75 | + }; |
| 76 | + var self = try Self.initSize(allocator, size); |
| 77 | + assert((std.fmt.bufPrint(self.list.items, format, args) catch unreachable).len == size); |
| 78 | + return self; |
| 79 | + } |
| 80 | + |
| 81 | + pub fn deinit(self: *Self) void { |
| 82 | + self.list.deinit(); |
| 83 | + } |
| 84 | + |
| 85 | + pub fn span(self: var) @TypeOf(self.list.items[0 .. self.list.len - 1 :sentinel]) { |
| 86 | + return self.list.span()[0..self.len() :sentinel]; |
| 87 | + } |
| 88 | + |
| 89 | + pub fn shrink(self: *Self, new_len: usize) void { |
| 90 | + assert(new_len <= self.len()); |
| 91 | + self.list.shrink(new_len + 1); |
| 92 | + self.list.items[self.len()] = sentinel; |
| 93 | + } |
| 94 | + |
| 95 | + pub fn resize(self: *Self, new_len: usize) !void { |
| 96 | + try self.list.resize(new_len + 1); |
| 97 | + self.list.items[self.len()] = sentinel; |
| 98 | + } |
| 99 | + |
| 100 | + pub fn isNull(self: Self) bool { |
| 101 | + return self.list.len == 0; |
| 102 | + } |
| 103 | + |
| 104 | + pub fn len(self: Self) usize { |
| 105 | + return self.list.len - 1; |
| 106 | + } |
| 107 | + |
| 108 | + pub fn capacity(self: Self) usize { |
| 109 | + return if (self.list.items.len > 0) |
| 110 | + self.list.items.len - 1 |
| 111 | + else |
| 112 | + 0; |
| 113 | + } |
| 114 | + |
| 115 | + pub fn appendSlice(self: *Self, m: []const T) !void { |
| 116 | + const old_len = self.len(); |
| 117 | + try self.resize(old_len + m.len); |
| 118 | + mem.copy(T, self.list.span()[old_len..], m); |
| 119 | + } |
| 120 | + |
| 121 | + pub fn append(self: *Self, byte: T) !void { |
| 122 | + const old_len = self.len(); |
| 123 | + try self.resize(old_len + 1); |
| 124 | + self.list.span()[old_len] = byte; |
| 125 | + } |
| 126 | + |
| 127 | + pub fn eql(self: Self, m: []const T) bool { |
| 128 | + return mem.eql(T, self.span(), m); |
| 129 | + } |
| 130 | + |
| 131 | + pub fn startsWith(self: Self, m: []const T) bool { |
| 132 | + if (self.len() < m.len) return false; |
| 133 | + return mem.eql(T, self.list.items[0..m.len], m); |
| 134 | + } |
| 135 | + |
| 136 | + pub fn endsWith(self: Self, m: []const T) bool { |
| 137 | + const l = self.len(); |
| 138 | + if (l < m.len) return false; |
| 139 | + const start = l - m.len; |
| 140 | + return mem.eql(T, self.list.items[start..l], m); |
| 141 | + } |
| 142 | + |
| 143 | + pub fn replaceContents(self: *Self, m: []const T) !void { |
| 144 | + try self.resize(m.len); |
| 145 | + mem.copy(T, self.list.span(), m); |
| 146 | + } |
| 147 | + |
| 148 | + /// Initializes an OutStream which will append to the list. |
| 149 | + /// This function may be called only when `T` is `u8`. |
| 150 | + pub fn outStream(self: *Self) std.io.OutStream(*Self, error{OutOfMemory}, appendWrite) { |
| 151 | + return .{ .context = self }; |
| 152 | + } |
| 153 | + |
| 154 | + /// Same as `append` except it returns the number of bytes written, which is always the same |
| 155 | + /// as `m.len`. The purpose of this function existing is to match `std.io.OutStream` API. |
| 156 | + /// This function may be called only when `T` is `u8`. |
| 157 | + pub fn appendWrite(self: *Self, m: []const u8) !usize { |
| 158 | + try self.appendSlice(m); |
| 159 | + return m.len; |
| 160 | + } |
| 161 | + }; |
| 162 | +} |
| 163 | + |
| 164 | +test "simple" { |
| 165 | + var buf = try ArrayListSentineled(u8, 0).init(testing.allocator, ""); |
| 166 | + defer buf.deinit(); |
| 167 | + |
| 168 | + testing.expect(buf.len() == 0); |
| 169 | + try buf.appendSlice("hello"); |
| 170 | + try buf.appendSlice(" "); |
| 171 | + try buf.appendSlice("world"); |
| 172 | + testing.expect(buf.eql("hello world")); |
| 173 | + testing.expect(mem.eql(u8, mem.spanZ(buf.span().ptr), buf.span())); |
| 174 | + |
| 175 | + var buf2 = try ArrayListSentineled(u8, 0).initFromBuffer(buf); |
| 176 | + defer buf2.deinit(); |
| 177 | + testing.expect(buf.eql(buf2.span())); |
| 178 | + |
| 179 | + testing.expect(buf.startsWith("hell")); |
| 180 | + testing.expect(buf.endsWith("orld")); |
| 181 | + |
| 182 | + try buf2.resize(4); |
| 183 | + testing.expect(buf.startsWith(buf2.span())); |
| 184 | +} |
| 185 | + |
| 186 | +test "initSize" { |
| 187 | + var buf = try ArrayListSentineled(u8, 0).initSize(testing.allocator, 3); |
| 188 | + defer buf.deinit(); |
| 189 | + testing.expect(buf.len() == 3); |
| 190 | + try buf.appendSlice("hello"); |
| 191 | + testing.expect(mem.eql(u8, buf.span()[3..], "hello")); |
| 192 | +} |
| 193 | + |
| 194 | +test "initCapacity" { |
| 195 | + var buf = try ArrayListSentineled(u8, 0).initCapacity(testing.allocator, 10); |
| 196 | + defer buf.deinit(); |
| 197 | + testing.expect(buf.len() == 0); |
| 198 | + testing.expect(buf.capacity() >= 10); |
| 199 | + const old_cap = buf.capacity(); |
| 200 | + try buf.appendSlice("hello"); |
| 201 | + testing.expect(buf.len() == 5); |
| 202 | + testing.expect(buf.capacity() == old_cap); |
| 203 | + testing.expect(mem.eql(u8, buf.span(), "hello")); |
| 204 | +} |
| 205 | + |
| 206 | +test "print" { |
| 207 | + var buf = try ArrayListSentineled(u8, 0).init(testing.allocator, ""); |
| 208 | + defer buf.deinit(); |
| 209 | + |
| 210 | + try buf.outStream().print("Hello {} the {}", .{ 2, "world" }); |
| 211 | + testing.expect(buf.eql("Hello 2 the world")); |
| 212 | +} |
| 213 | + |
| 214 | +test "outStream" { |
| 215 | + var buffer = try ArrayListSentineled(u8, 0).initSize(testing.allocator, 0); |
| 216 | + defer buffer.deinit(); |
| 217 | + const buf_stream = buffer.outStream(); |
| 218 | + |
| 219 | + const x: i32 = 42; |
| 220 | + const y: i32 = 1234; |
| 221 | + try buf_stream.print("x: {}\ny: {}\n", .{ x, y }); |
| 222 | + |
| 223 | + testing.expect(mem.eql(u8, buffer.span(), "x: 42\ny: 1234\n")); |
| 224 | +} |
0 commit comments