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