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 buffer that allocates memory and maintains a null byte at the end. pub const Buffer = struct { list: ArrayList(u8), /// Must deinitialize with deinit. pub fn init(allocator: *Allocator, m: []const u8) !Buffer { var self = try initSize(allocator, m.len); mem.copy(u8, 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) !Buffer { 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) !Buffer { var self = Buffer{ .list = try ArrayList(u8).initCapacity(allocator, num + 1) }; self.list.appendAssumeCapacity(0); 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) Buffer { return Buffer{ .list = ArrayList(u8).init(allocator) }; } /// Must deinitialize with deinit. pub fn initFromBuffer(buffer: Buffer) !Buffer { return Buffer.init(buffer.list.allocator, buffer.toSliceConst()); } /// Buffer 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: []u8) !Buffer { var self = Buffer{ .list = ArrayList(u8).fromOwnedSlice(allocator, slice) }; try self.list.append(0); return self; } /// The caller owns the returned memory. The Buffer becomes null and /// is safe to `deinit`. pub fn toOwnedSlice(self: *Buffer) [:0]u8 { const allocator = self.list.allocator; const result = self.list.toOwnedSlice(); self.* = initNull(allocator); return result[0 .. result.len - 1 :0]; } pub fn allocPrint(allocator: *Allocator, comptime format: []const u8, args: var) !Buffer { const countSize = struct { fn countSize(size: *usize, bytes: []const u8) (error{}!void) { size.* += bytes.len; } }.countSize; var size: usize = 0; std.fmt.format(&size, error{}, countSize, format, args) catch |err| switch (err) {}; var self = try Buffer.initSize(allocator, size); assert((std.fmt.bufPrint(self.list.items, format, args) catch unreachable).len == size); return self; } pub fn deinit(self: *Buffer) void { self.list.deinit(); } pub fn span(self: var) @TypeOf(self.list.items[0 .. self.list.len - 1 :0]) { return self.list.span()[0..self.len() :0]; } /// Deprecated: use `span` pub fn toSlice(self: Buffer) [:0]u8 { return self.span(); } /// Deprecated: use `span` pub fn toSliceConst(self: Buffer) [:0]const u8 { return self.span(); } pub fn shrink(self: *Buffer, new_len: usize) void { assert(new_len <= self.len()); self.list.shrink(new_len + 1); self.list.items[self.len()] = 0; } pub fn resize(self: *Buffer, new_len: usize) !void { try self.list.resize(new_len + 1); self.list.items[self.len()] = 0; } pub fn isNull(self: Buffer) bool { return self.list.len == 0; } pub fn len(self: Buffer) usize { return self.list.len - 1; } pub fn capacity(self: Buffer) usize { return if (self.list.items.len > 0) self.list.items.len - 1 else 0; } pub fn append(self: *Buffer, m: []const u8) !void { const old_len = self.len(); try self.resize(old_len + m.len); mem.copy(u8, self.list.toSlice()[old_len..], m); } pub fn appendByte(self: *Buffer, byte: u8) !void { const old_len = self.len(); try self.resize(old_len + 1); self.list.toSlice()[old_len] = byte; } pub fn eql(self: Buffer, m: []const u8) bool { return mem.eql(u8, self.toSliceConst(), m); } pub fn startsWith(self: Buffer, m: []const u8) bool { if (self.len() < m.len) return false; return mem.eql(u8, self.list.items[0..m.len], m); } pub fn endsWith(self: Buffer, m: []const u8) bool { const l = self.len(); if (l < m.len) return false; const start = l - m.len; return mem.eql(u8, self.list.items[start..l], m); } pub fn replaceContents(self: *Buffer, m: []const u8) !void { try self.resize(m.len); mem.copy(u8, self.list.toSlice(), m); } pub fn print(self: *Buffer, comptime fmt: []const u8, args: var) !void { return std.fmt.format(self, error{OutOfMemory}, Buffer.append, fmt, args); } pub fn outStream(self: *Buffer) std.io.OutStream(*Buffer, 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. pub fn appendWrite(self: *Buffer, m: []const u8) !usize { try self.append(m); return m.len; } }; test "simple Buffer" { var buf = try Buffer.init(testing.allocator, ""); defer buf.deinit(); testing.expect(buf.len() == 0); try buf.append("hello"); try buf.append(" "); try buf.append("world"); testing.expect(buf.eql("hello world")); testing.expect(mem.eql(u8, mem.toSliceConst(u8, buf.toSliceConst().ptr), buf.toSliceConst())); var buf2 = try Buffer.initFromBuffer(buf); defer buf2.deinit(); testing.expect(buf.eql(buf2.toSliceConst())); testing.expect(buf.startsWith("hell")); testing.expect(buf.endsWith("orld")); try buf2.resize(4); testing.expect(buf.startsWith(buf2.toSlice())); } test "Buffer.initSize" { var buf = try Buffer.initSize(testing.allocator, 3); defer buf.deinit(); testing.expect(buf.len() == 3); try buf.append("hello"); testing.expect(mem.eql(u8, buf.toSliceConst()[3..], "hello")); } test "Buffer.initCapacity" { var buf = try Buffer.initCapacity(testing.allocator, 10); defer buf.deinit(); testing.expect(buf.len() == 0); testing.expect(buf.capacity() >= 10); const old_cap = buf.capacity(); try buf.append("hello"); testing.expect(buf.len() == 5); testing.expect(buf.capacity() == old_cap); testing.expect(mem.eql(u8, buf.toSliceConst(), "hello")); } test "Buffer.print" { var buf = try Buffer.init(testing.allocator, ""); defer buf.deinit(); try buf.print("Hello {} the {}", .{ 2, "world" }); testing.expect(buf.eql("Hello 2 the world")); } test "Buffer.outStream" { var buffer = try Buffer.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.toSlice(), "x: 42\ny: 1234\n")); }