Something went wrong. Try again.
zig utility library forked from karlseguin.tngl.sh/zul
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143const std = @import("std");const builtin = @import("builtin");const string_builder = @This();
const Mutex = std.Io.Mutex;const Endian = std.builtin.Endian;const Allocator = std.mem.Allocator;
pub const StringBuilder = struct { buf: []u8, pos: usize, static: []u8, pool: ?*string_builder.Pool = null, endian: Endian = builtin.cpu.arch.endian(), allocator: Allocator,
pub const Pool = string_builder.Pool;
// This is for one-off use. It's like creating an std.ArrayList(u8). We won't // use static at all, and everything will just be dynamic. pub fn init(allocator: Allocator) StringBuilder { return .{ .pos = 0, .pool = null, .buf = &[_]u8{}, .static = &[_]u8{}, .allocator = allocator, }; }
// This is being created by our Pool, either in Pool.init or lazily in // pool.acquire(). The idea is that this buffer will get re-used so it has // a static buffer that will get used, and we'll only need to dynamically // allocate memory beyond static if we try to write more than static.len. fn initForPool(allocator: Allocator, pool: *string_builder.Pool, static_size: usize) !StringBuilder { const static = try allocator.alloc(u8, static_size); return .{ .pos = 0, .pool = pool, .buf = static, .static = static, .allocator = allocator, }; }
// buf must be created with allocator pub fn fromOwnedSlice(allocator: Allocator, buf: []u8) StringBuilder { return .{ .buf = buf, .pool = null, .pos = buf.len, .static = &[_]u8{}, .allocator = allocator, }; }
pub const FromReaderOpts = struct { max_size: usize = std.math.maxInt(usize), buffer_size: usize = 8192, };
pub fn fromReader(allocator: Allocator, reader: *std.Io.Reader, opts: FromReaderOpts) !StringBuilder { const max_size = opts.max_size; const buffer_size = if (opts.buffer_size < 64) 64 else opts.buffer_size;
var buf = try allocator.alloc(u8, buffer_size); errdefer allocator.free(buf);
var pos: usize = 0; while (true) { var read_slice = buf[pos..]; if (read_slice.len < 512) { const new_capacity = buf.len + buffer_size; if (allocator.resize(buf, new_capacity)) { buf = buf.ptr[0..new_capacity]; } else { const new_buffer = try allocator.alloc(u8, new_capacity); @memcpy(new_buffer[0..buf.len], buf); allocator.free(buf); buf = new_buffer; } read_slice = buf[pos..]; }
const n = try reader.readSliceShort(read_slice); if (n == 0) { break; }
pos += n; if (pos > max_size) { return error.TooBig; } }
var sb = fromOwnedSlice(allocator, buf); sb.pos = pos; return sb; }
pub fn deinit(self: *const StringBuilder) void { self.allocator.free(self.buf); }
// it's a mistake to call release if this string builder isn't from a pool pub fn release(self: *StringBuilder) void { const p = self.pool orelse unreachable; p.release(self); }
pub fn clearRetainingCapacity(self: *StringBuilder) void { self.pos = 0; }
pub fn len(self: *const StringBuilder) usize { return self.pos; }
pub fn string(self: *const StringBuilder) []u8 { return self.buf[0..self.pos]; }
pub fn stringZ(self: *StringBuilder) ![:0]u8 { const pos = self.pos; try self.writeByte(0); self.pos = pos; return self.buf[0..pos :0]; }
pub fn copy(self: *const StringBuilder, allocator: Allocator) ![]u8 { const pos = self.pos; const c = try allocator.alloc(u8, pos); @memcpy(c, self.buf[0..pos]); return c; }
pub fn truncate(self: *StringBuilder, n: usize) void { const pos = self.pos; if (n >= pos) { self.pos = 0; return; } self.pos = pos - n; }
pub fn skip(self: *StringBuilder, n: usize) !View { try self.ensureUnusedCapacity(n); const pos = self.pos; self.pos = pos + n; return .{ .pos = pos, .sb = self, }; }
pub fn writeByte(self: *StringBuilder, b: u8) !void { try self.ensureUnusedCapacity(1); self.writeByteAssumeCapacity(b); }
pub fn writeByteAssumeCapacity(self: *StringBuilder, b: u8) void { const pos = self.pos; writeByteInto(self.buf, pos, b); self.pos = pos + 1; }
pub fn writeByteNTimes(self: *StringBuilder, b: u8, n: usize) !void { try self.ensureUnusedCapacity(n); const pos = self.pos; writeByteNTimesInto(self.buf, pos, b, n); self.pos = pos + n; }
pub fn write(self: *StringBuilder, data: []const u8) !void { try self.ensureUnusedCapacity(data.len); self.writeAssumeCapacity(data); }
pub fn writeAssumeCapacity(self: *StringBuilder, data: []const u8) void { const pos = self.pos; writeInto(self.buf, pos, data); self.pos = pos + data.len; }
pub fn writeU16(self: *StringBuilder, value: u16) !void { return self.writeIntT(u16, value, self.endian); }
pub fn writeI16(self: *StringBuilder, value: i16) !void { return self.writeIntT(i16, value, self.endian); }
pub fn writeU32(self: *StringBuilder, value: u32) !void { return self.writeIntT(u32, value, self.endian); }
pub fn writeI32(self: *StringBuilder, value: i32) !void { return self.writeIntT(i32, value, self.endian); }
pub fn writeU64(self: *StringBuilder, value: u64) !void { return self.writeIntT(u64, value, self.endian); }
pub fn writeI64(self: *StringBuilder, value: i64) !void { return self.writeIntT(i64, value, self.endian); }
pub fn writeU16Little(self: *StringBuilder, value: u16) !void { return self.writeIntT(u16, value, .little); }
pub fn writeI16Little(self: *StringBuilder, value: i16) !void { return self.writeIntT(i16, value, .little); }
pub fn writeU32Little(self: *StringBuilder, value: u32) !void { return self.writeIntT(u32, value, .little); }
pub fn writeI32Little(self: *StringBuilder, value: i32) !void { return self.writeIntT(i32, value, .little); }
pub fn writeU64Little(self: *StringBuilder, value: u64) !void { return self.writeIntT(u64, value, .little); }
pub fn writeI64Little(self: *StringBuilder, value: i64) !void { return self.writeIntT(i64, value, .little); }
pub fn writeU16Big(self: *StringBuilder, value: u16) !void { return self.writeIntT(u16, value, .big); }
pub fn writeI16Big(self: *StringBuilder, value: i16) !void { return self.writeIntT(i16, value, .big); }
pub fn writeU32Big(self: *StringBuilder, value: u32) !void { return self.writeIntT(u32, value, .big); }
pub fn writeI32Big(self: *StringBuilder, value: i32) !void { return self.writeIntT(i32, value, .big); }
pub fn writeU64Big(self: *StringBuilder, value: u64) !void { return self.writeIntT(u64, value, .big); }
pub fn writeI64Big(self: *StringBuilder, value: i64) !void { return self.writeIntT(i64, value, .big); }
fn writeIntT(self: *StringBuilder, comptime T: type, value: T, endian: Endian) !void { const l = @divExact(@typeInfo(T).int.bits, 8); try self.ensureUnusedCapacity(l); const pos = self.pos; writeIntInto(T, self.buf, pos, value, l, endian); self.pos = pos + l; }
pub fn writeInt(self: *StringBuilder, value: anytype) !void { return self.writeIntAs(value, self.endian); }
pub fn writeIntAs(self: *StringBuilder, value: anytype, endian: Endian) !void { const T = @TypeOf(value); switch (@typeInfo(T)) { .comptime_int => @compileError("Writing a comptime_int is slightly ambiguous, please cast to a specific type: sb.writeInt(@as(i32, 9001))"), .int => |int| { if (int.signedness == .signed) { switch (int.bits) { 8 => return self.writeByte(value), 16 => return self.writeIntT(i16, value, endian), 32 => return self.writeIntT(i32, value, endian), 64 => return self.writeIntT(i64, value, endian), else => {}, } } else { switch (int.bits) { 8 => return self.writeByte(value), 16 => return self.writeIntT(u16, value, endian), 32 => return self.writeIntT(u32, value, endian), 64 => return self.writeIntT(u64, value, endian), else => {}, } } }, else => {}, } @compileError("Unsupported integer type: " ++ @typeName(T)); }
pub fn ensureUnusedCapacity(self: *StringBuilder, n: usize) !void { return self.ensureTotalCapacity(self.pos + n); }
pub fn ensureTotalCapacity(self: *StringBuilder, required_capacity: usize) !void { const buf = self.buf; if (required_capacity <= buf.len) { return; }
// from std.ArrayList var new_capacity = buf.len; while (true) { new_capacity +|= new_capacity / 2 + 8; if (new_capacity >= required_capacity) break; }
const is_static = self.buf.ptr == self.static.ptr;
const allocator = self.allocator; if (is_static and allocator.resize(buf, new_capacity)) { self.buf = buf.ptr[0..new_capacity]; return; } const new_buffer = try allocator.alloc(u8, new_capacity); @memcpy(new_buffer[0..buf.len], buf);
if (!is_static) { // we don't free the static buffer allocator.free(buf); } self.buf = new_buffer; }
pub fn writer(self: *StringBuilder) Writer { return .init(self); }
pub const Writer = struct { sb: *StringBuilder, interface: std.Io.Writer,
pub const Error = Allocator.Error;
fn init(sb: *StringBuilder) Writer { return .{ .sb = sb, .interface = .{ .vtable = &.{ .drain = drain, }, .buffer = &.{}, }, }; }
pub fn drain(io_w: *std.Io.Writer, data: []const []const u8, splat: usize) error{WriteFailed}!usize { _ = splat; const self: *Writer = @alignCast(@fieldParentPtr("interface", io_w)); self.sb.write(data[0]) catch return error.WriteFailed; return data[0].len; }
pub fn print(self: *Writer, comptime fmt: []const u8, args: anytype) !void { return self.interface.print(fmt, args) catch return error.OutOfMemory; }
pub fn write(self: Writer, data: []const u8) Allocator.Error!usize { try self.sb.write(data); return data.len; }
pub fn writeAll(self: Writer, data: []const u8) Allocator.Error!void { try self.sb.write(data); }
pub fn writeByte(self: Writer, b: u8) Allocator.Error!void { try self.sb.writeByte(b); } pub fn writeByteNTimes(self: Writer, b: u8, n: usize) !void { return self.sb.writeByteNTimes(b, n); }
pub fn adaptToNewApi(self: Writer) Adapter { return .{ .new_interface = self.interface }; }
pub const Adapter = struct { err: ?Error = null, new_interface: std.Io.Writer, }; };};
pub const View = struct { pos: usize, sb: *StringBuilder,
pub fn writeByte(self: *View, b: u8) void { const pos = self.pos; writeByteInto(self.sb.buf, pos, b); self.pos = pos + 1; }
pub fn writeByteNTimes(self: *View, b: u8, n: usize) void { const pos = self.pos; writeByteNTimesInto(self.sb.buf, pos, b, n); self.pos = pos + n; }
pub fn write(self: *View, data: []const u8) void { const pos = self.pos; writeInto(self.sb.buf, pos, data); self.pos = pos + data.len; }
pub fn writeU16(self: *View, value: u16) void { return self.writeIntT(u16, value, self.endian); }
pub fn writeI16(self: *View, value: i16) void { return self.writeIntT(i16, value, self.endian); }
pub fn writeU32(self: *View, value: u32) void { return self.writeIntT(u32, value, self.endian); }
pub fn writeI32(self: *View, value: i32) void { return self.writeIntT(i32, value, self.endian); }
pub fn writeU64(self: *View, value: u64) void { return self.writeIntT(u64, value, self.endian); }
pub fn writeI64(self: *View, value: i64) void { return self.writeIntT(i64, value, self.endian); }
pub fn writeU16Little(self: *View, value: u16) void { return self.writeIntT(u16, value, .little); }
pub fn writeI16Little(self: *View, value: i16) void { return self.writeIntT(i16, value, .little); }
pub fn writeU32Little(self: *View, value: u32) void { return self.writeIntT(u32, value, .little); }
pub fn writeI32Little(self: *View, value: i32) void { return self.writeIntT(i32, value, .little); }
pub fn writeU64Little(self: *View, value: u64) void { return self.writeIntT(u64, value, .little); }
pub fn writeI64Little(self: *View, value: i64) void { return self.writeIntT(i64, value, .little); }
pub fn writeU16Big(self: *View, value: u16) void { return self.writeIntT(u16, value, .big); }
pub fn writeI16Big(self: *View, value: i16) void { return self.writeIntT(i16, value, .big); }
pub fn writeU32Big(self: *View, value: u32) void { return self.writeIntT(u32, value, .big); }
pub fn writeI32Big(self: *View, value: i32) void { return self.writeIntT(i32, value, .big); }
pub fn writeU64Big(self: *View, value: u64) void { return self.writeIntT(u64, value, .big); }
pub fn writeI64Big(self: *View, value: i64) void { return self.writeIntT(i64, value, .big); }
fn writeIntT(self: *View, comptime T: type, value: T, endian: Endian) void { const l = @divExact(@typeInfo(T).int.bits, 8); const pos = self.pos; writeIntInto(T, self.sb.buf, pos, value, l, endian); self.pos = pos + l; }
pub fn writeInt(self: *View, value: anytype) void { return self.writeIntAs(value, self.endian); }
pub fn writeIntAs(self: *View, value: anytype, endian: Endian) void { const T = @TypeOf(value); switch (@typeInfo(T)) { .comptime_int => @compileError("Writing a comptime_int is slightly ambiguous, please cast to a specific type: sb.writeInt(@as(i32, 9001))"), .int => |int| { if (int.signedness == .signed) { switch (int.bits) { 8 => return self.writeByte(value), 16 => return self.writeIntT(i16, value, endian), 32 => return self.writeIntT(i32, value, endian), 64 => return self.writeIntT(i64, value, endian), else => {}, } } else { switch (int.bits) { 8 => return self.writeByte(value), 16 => return self.writeIntT(u16, value, endian), 32 => return self.writeIntT(u32, value, endian), 64 => return self.writeIntT(u64, value, endian), else => {}, } } }, else => {}, } @compileError("Unsupported integer type: " ++ @typeName(T)); }};
pub const Pool = struct { mutex: Mutex, available: usize, allocator: Allocator, static_size: usize, builders: []*StringBuilder, io: std.Io,
pub fn init(io: std.Io, allocator: Allocator, pool_size: u16, static_size: usize) !*Pool { const builders = try allocator.alloc(*StringBuilder, pool_size); errdefer allocator.free(builders);
const pool = try allocator.create(Pool); errdefer allocator.destroy(pool);
pool.* = .{ .mutex = .init, .builders = builders, .allocator = allocator, .available = pool_size, .static_size = static_size, .io = io, };
var allocated: usize = 0; errdefer { for (0..allocated) |i| { var sb = builders[i]; sb.deinit(); allocator.destroy(sb); } }
for (0..pool_size) |i| { const sb = try allocator.create(StringBuilder); errdefer allocator.destroy(sb); sb.* = try StringBuilder.initForPool(allocator, pool, static_size); builders[i] = sb; allocated += 1; }
return pool; }
pub fn deinit(self: *Pool) void { const allocator = self.allocator; for (self.builders) |sb| { sb.deinit(); allocator.destroy(sb); } allocator.free(self.builders); allocator.destroy(self); }
pub fn acquire(self: *Pool) !*StringBuilder { const builders = self.builders;
self.mutex.lockUncancelable(self.io); const available = self.available; if (available == 0) { // dont hold the lock over factory self.mutex.unlock(self.io);
const allocator = self.allocator; const sb = try allocator.create(StringBuilder); // Intentionally not using initForPool here. There's a tradeoff. // If we use initForPool, than this StringBuilder could be re-added to the // pool on release, which would help keep our pool nice and full. However, // many applications will use a very large static_size to avoid or minimize // dynamic allocations and grows/copies. They do this thinking all of that // static buffers are allocated upfront, on startup. Doing it here would // result in an unexpected large allocation, the exact opposite of what // we're after. sb.* = StringBuilder.init(allocator); // even though we wont' release this back to the pool, we still want // sb.release() to be callable. sb.release() will call pool.release() // which will know what to do with this non-pooled StringBuilder. sb.pool = self; return sb; } const index = available - 1; const sb = builders[index]; self.available = index; self.mutex.unlock(self.io); return sb; }
pub fn release(self: *Pool, sb: *StringBuilder) void { const allocator = self.allocator;
if (sb.static.len == 0) { // this buffer was allocated by acquire() because the pool was empty // it has no static buffer, so we release it allocator.free(sb.buf); allocator.destroy(sb); return; }
sb.pos = 0; if (sb.buf.ptr != sb.static.ptr) { // If buf.ptr != static.ptr, that means we had to dymamically allocate a // buffer beyond static. Free that dynamically allocated buffer... allocator.free(sb.buf); // ... and restore the static buffer; sb.buf = sb.static; }
self.mutex.lockUncancelable(self.io); const available = self.available; var builders = self.builders; builders[available] = sb; self.available = available + 1; self.mutex.unlock(self.io); }};
// Functions that write for either a *StringBuilder or a *Viewinline fn writeInto(buf: []u8, pos: usize, data: []const u8) void { const end_pos = pos + data.len; @memcpy(buf[pos..end_pos], data);}
inline fn writeByteInto(buf: []u8, pos: usize, b: u8) void { buf[pos] = b;}
inline fn writeByteNTimesInto(buf: []u8, pos: usize, b: u8, n: usize) void { for (0..n) |offset| { buf[pos + offset] = b; }}
inline fn writeIntInto(comptime T: type, buf: []u8, pos: usize, value: T, l: usize, endian: Endian) void { const end_pos = pos + l; std.mem.writeInt(T, buf[pos..end_pos][0..l], value, endian);}
const t = @import("zul.zig").testing;test "StringBuilder: doc example" { var sb = StringBuilder.init(t.allocator); defer sb.deinit();
var view = try sb.skip(4); try sb.writeByte(10); try sb.write("hello"); view.writeU32Big(@intCast(sb.len() - 4)); try t.expectEqual(&.{ 0, 0, 0, 6, 10, 'h', 'e', 'l', 'l', 'o' }, sb.string());}
test "StringBuilder: growth" { var sb = StringBuilder.init(t.allocator); defer sb.deinit();
// we clearRetainingCapacity at the end of the loop, and things should work // the same the second time for (0..2) |_| { try t.expectEqual(0, sb.len()); try sb.writeByte('o'); try t.expectEqual(1, sb.len()); try t.expectEqual("o", sb.string());
// stays in static try sb.write("ver 9000!"); try t.expectEqual(10, sb.len()); try t.expectEqual("over 9000!", sb.string());
// grows into dynamic try sb.write("!!!"); try t.expectEqual(13, sb.len()); try t.expectEqual("over 9000!!!!", sb.string());
try sb.write("If you were to run this code, you'd almost certainly see a segmentation fault (aka, segfault). We create a Response which involves creating an ArenaAllocator and from that, an Allocator. This allocator is then used to format our string. For the purpose of this example, we create a 2nd response and immediately free it. We need this for the same reason that warning1 in our first example printed an almost ok value: we want to re-initialize the memory in our init function stack."); try t.expectEqual(492, sb.len()); try t.expectEqual("over 9000!!!!If you were to run this code, you'd almost certainly see a segmentation fault (aka, segfault). We create a Response which involves creating an ArenaAllocator and from that, an Allocator. This allocator is then used to format our string. For the purpose of this example, we create a 2nd response and immediately free it. We need this for the same reason that warning1 in our first example printed an almost ok value: we want to re-initialize the memory in our init function stack.", sb.string());
sb.clearRetainingCapacity(); }}
test "StringBuilder: truncate" { var sb = StringBuilder.init(t.allocator); defer sb.deinit();
sb.truncate(100); try t.expectEqual(0, sb.len());
try sb.write("hello world!1");
sb.truncate(0); try t.expectEqual(13, sb.len()); try t.expectEqual("hello world!1", sb.string());
sb.truncate(1); try t.expectEqual(12, sb.len()); try t.expectEqual("hello world!", sb.string());
sb.truncate(5); try t.expectEqual(7, sb.len()); try t.expectEqual("hello w", sb.string());}
test "StringBuilder: fuzz" { defer t.reset();
var control: std.ArrayListUnmanaged(u8) = .empty; defer control.deinit(t.allocator);
for (1..25) |_| { var sb = StringBuilder.init(t.allocator); defer sb.deinit();
for (1..25) |_| { var buf: [30]u8 = undefined; const input = t.Random.fillAtLeast(&buf, 1); try sb.write(input); try control.appendSlice(t.allocator, input); try t.expectEqual(control.items, sb.string()); } control.clearRetainingCapacity(); }}
test "StringBuilder: writer json" { var sb = StringBuilder.init(t.allocator); defer sb.deinit();
var writer = sb.writer(); try std.json.Stringify.value(.{ .over = 9000, .spice = "must flow", .ok = true }, .{}, &writer.interface); try t.expectEqual("{\"over\":9000,\"spice\":\"must flow\",\"ok\":true}", sb.string());}
test "StringBuilder: writer direct" { var sb = StringBuilder.init(t.allocator); defer sb.deinit();
var writer = sb.writer(); try writer.writeByte('9'); try writer.writeAll("9000"); try t.expectEqual(2, try writer.write("!!")); try t.expectEqual("99000!!", sb.string());}
test "StringBuilder: copy" { var sb = StringBuilder.init(t.allocator); defer sb.deinit();
try sb.write("hello!!"); const c = try sb.copy(t.allocator); defer t.allocator.free(c); try t.expectEqual("hello!!", c);}
test "StringBuilder: write little" { var sb = StringBuilder.init(t.allocator); defer sb.deinit();
{ // unsigned try sb.writeU64Little(11234567890123456789); try t.expectEqual(&[_]u8{ 21, 129, 209, 7, 249, 51, 233, 155 }, sb.string());
try sb.writeU32Little(3283856184); try t.expectEqual(&[_]u8{ 21, 129, 209, 7, 249, 51, 233, 155, 56, 171, 187, 195 }, sb.string());
try sb.writeU16Little(15000); try t.expectEqual(&[_]u8{ 21, 129, 209, 7, 249, 51, 233, 155, 56, 171, 187, 195, 152, 58 }, sb.string()); }
{ // signed sb.clearRetainingCapacity(); try sb.writeI64Little(-1123456789012345678); try t.expectEqual(&[_]u8{ 178, 12, 107, 178, 0, 174, 104, 240 }, sb.string());
try sb.writeI32Little(-328385618); try t.expectEqual(&[_]u8{ 178, 12, 107, 178, 0, 174, 104, 240, 174, 59, 109, 236 }, sb.string());
try sb.writeI16Little(-15001); try t.expectEqual(&[_]u8{ 178, 12, 107, 178, 0, 174, 104, 240, 174, 59, 109, 236, 103, 197 }, sb.string()); }
{ // writeXYZ with sb.endian == .litle, unsigned sb.clearRetainingCapacity(); sb.endian = .little; try sb.writeU64(11234567890123456789); try t.expectEqual(&[_]u8{ 21, 129, 209, 7, 249, 51, 233, 155 }, sb.string());
try sb.writeU32(3283856184); try t.expectEqual(&[_]u8{ 21, 129, 209, 7, 249, 51, 233, 155, 56, 171, 187, 195 }, sb.string());
try sb.writeU16(15000); try t.expectEqual(&[_]u8{ 21, 129, 209, 7, 249, 51, 233, 155, 56, 171, 187, 195, 152, 58 }, sb.string()); }
{ // writeXYZ with sb.endian == .litle, signed sb.clearRetainingCapacity(); sb.endian = .little; try sb.writeI64(-1123456789012345678); try t.expectEqual(&[_]u8{ 178, 12, 107, 178, 0, 174, 104, 240 }, sb.string());
try sb.writeI32(-328385618); try t.expectEqual(&[_]u8{ 178, 12, 107, 178, 0, 174, 104, 240, 174, 59, 109, 236 }, sb.string());
try sb.writeI16(-15001); try t.expectEqual(&[_]u8{ 178, 12, 107, 178, 0, 174, 104, 240, 174, 59, 109, 236, 103, 197 }, sb.string()); }
{ // writeInt with sb.endian == .litle, unsigned sb.clearRetainingCapacity(); sb.endian = .little; try sb.writeInt(@as(u64, 11234567890123456789)); try t.expectEqual(&[_]u8{ 21, 129, 209, 7, 249, 51, 233, 155 }, sb.string());
try sb.writeInt(@as(u32, 3283856184)); try t.expectEqual(&[_]u8{ 21, 129, 209, 7, 249, 51, 233, 155, 56, 171, 187, 195 }, sb.string());
try sb.writeInt(@as(u16, 15000)); try t.expectEqual(&[_]u8{ 21, 129, 209, 7, 249, 51, 233, 155, 56, 171, 187, 195, 152, 58 }, sb.string()); }
{ // writeInt with sb.endian == .litle, signed sb.clearRetainingCapacity(); sb.endian = .little; try sb.writeInt(@as(i64, -1123456789012345678)); try t.expectEqual(&[_]u8{ 178, 12, 107, 178, 0, 174, 104, 240 }, sb.string());
try sb.writeInt(@as(i32, -328385618)); try t.expectEqual(&[_]u8{ 178, 12, 107, 178, 0, 174, 104, 240, 174, 59, 109, 236 }, sb.string());
try sb.writeInt(@as(i16, -15001)); try t.expectEqual(&[_]u8{ 178, 12, 107, 178, 0, 174, 104, 240, 174, 59, 109, 236, 103, 197 }, sb.string()); }}
test "StringBuilder: write big" { var sb = StringBuilder.init(t.allocator); defer sb.deinit(); { // unsigned try sb.writeU64Big(11234567890123456789); try t.expectEqual(&[_]u8{ 155, 233, 51, 249, 7, 209, 129, 21 }, sb.string());
try sb.writeU32Big(3283856184); try t.expectEqual(&[_]u8{ 155, 233, 51, 249, 7, 209, 129, 21, 195, 187, 171, 56 }, sb.string());
try sb.writeU16Big(15000); try t.expectEqual(&[_]u8{ 155, 233, 51, 249, 7, 209, 129, 21, 195, 187, 171, 56, 58, 152 }, sb.string()); }
{ // signed sb.clearRetainingCapacity(); try sb.writeI64Big(-1123456789012345678); try t.expectEqual(&[_]u8{ 240, 104, 174, 0, 178, 107, 12, 178 }, sb.string());
try sb.writeI32Big(-328385618); try t.expectEqual(&[_]u8{ 240, 104, 174, 0, 178, 107, 12, 178, 236, 109, 59, 174 }, sb.string());
try sb.writeI16Big(-15001); try t.expectEqual(&[_]u8{ 240, 104, 174, 0, 178, 107, 12, 178, 236, 109, 59, 174, 197, 103 }, sb.string()); }
{ // writeXYZ with sb.endian == .litle, unsigned sb.clearRetainingCapacity(); sb.endian = .big; try sb.writeU64(11234567890123456789); try t.expectEqual(&[_]u8{ 155, 233, 51, 249, 7, 209, 129, 21 }, sb.string());
try sb.writeU32(3283856184); try t.expectEqual(&[_]u8{ 155, 233, 51, 249, 7, 209, 129, 21, 195, 187, 171, 56 }, sb.string());
try sb.writeU16(15000); try t.expectEqual(&[_]u8{ 155, 233, 51, 249, 7, 209, 129, 21, 195, 187, 171, 56, 58, 152 }, sb.string()); }
{ // writeXYZ with sb.endian == .litle, signed sb.clearRetainingCapacity(); sb.endian = .big; try sb.writeI64(-1123456789012345678); try t.expectEqual(&[_]u8{ 240, 104, 174, 0, 178, 107, 12, 178 }, sb.string());
try sb.writeI32(-328385618); try t.expectEqual(&[_]u8{ 240, 104, 174, 0, 178, 107, 12, 178, 236, 109, 59, 174 }, sb.string());
try sb.writeI16(-15001); try t.expectEqual(&[_]u8{ 240, 104, 174, 0, 178, 107, 12, 178, 236, 109, 59, 174, 197, 103 }, sb.string()); }
{ // wrinteInt with sb.endian == .big, unsigned sb.clearRetainingCapacity(); sb.endian = .big; try sb.writeInt(@as(u64, 11234567890123456789)); try t.expectEqual(&[_]u8{ 155, 233, 51, 249, 7, 209, 129, 21 }, sb.string());
try sb.writeInt(@as(u32, 3283856184)); try t.expectEqual(&[_]u8{ 155, 233, 51, 249, 7, 209, 129, 21, 195, 187, 171, 56 }, sb.string());
try sb.writeInt(@as(u16, 15000)); try t.expectEqual(&[_]u8{ 155, 233, 51, 249, 7, 209, 129, 21, 195, 187, 171, 56, 58, 152 }, sb.string()); }
{ // writeInt with sb.endian == .big, signed sb.clearRetainingCapacity(); sb.endian = .big; try sb.writeInt(@as(i64, -1123456789012345678)); try t.expectEqual(&[_]u8{ 240, 104, 174, 0, 178, 107, 12, 178 }, sb.string());
try sb.writeInt(@as(i32, -328385618)); try t.expectEqual(&[_]u8{ 240, 104, 174, 0, 178, 107, 12, 178, 236, 109, 59, 174 }, sb.string());
try sb.writeInt(@as(i16, -15001)); try t.expectEqual(&[_]u8{ 240, 104, 174, 0, 178, 107, 12, 178, 236, 109, 59, 174, 197, 103 }, sb.string()); }}
test "StringBuilder: skip" { var sb = StringBuilder.init(t.allocator); defer sb.deinit();
{ try sb.writeByte('!'); var view = try sb.skip(3); view.write("123");
try t.expectEqual("!123", sb.string()); }
{ sb.clearRetainingCapacity(); try sb.writeByte('D'); var view = try sb.skip(2); view.writeU16Little(9001); try t.expectEqual(&.{ 'D', 41, 35 }, sb.string()); }}
test "StringBuilder: fromOwnedSlice" { const s = try t.allocator.alloc(u8, 5); @memcpy(s, "hello");
const sb = StringBuilder.fromOwnedSlice(t.allocator, s); try t.expectEqual("hello", sb.string()); sb.deinit();}
test "StringBuilder: fromReader" { var buf: [5000]u8 = undefined; t.Random.fill(&buf);
{ // input too large var reader = std.Io.Reader.fixed(&buf); try t.expectEqual(error.TooBig, StringBuilder.fromReader(t.allocator, &reader, .{ .max_size = 1, })); }
{ // input too large (just) var reader = std.Io.Reader.fixed(&buf); try t.expectEqual(error.TooBig, StringBuilder.fromReader(t.allocator, &reader, .{ .max_size = 4999, })); }
{ // test with larger buffer than input var reader = std.Io.Reader.fixed(&buf); const sb = try StringBuilder.fromReader(t.allocator, &reader, .{ .buffer_size = 6000, }); defer sb.deinit(); try t.expectEqual(&buf, sb.string()); }
// test with different buffer sizes for (0..50) |_| { var reader = std.Io.Reader.fixed(&buf); const sb = try StringBuilder.fromReader(t.allocator, &reader, .{ .buffer_size = t.Random.intRange(u16, 510, 5000), }); defer sb.deinit(); try t.expectEqual(&buf, sb.string()); }}
test "StringBuilder: stringZ" { var sb = StringBuilder.init(t.allocator); defer sb.deinit();
{ try t.expectEqual("", try sb.stringZ()); }
{ sb.clearRetainingCapacity(); try sb.write("123"); try sb.write("b"); try t.expectEqual("123b", try sb.stringZ()); }
{ sb.clearRetainingCapacity(); try sb.write("123"); try t.expectEqual("123", try sb.stringZ());
try sb.write("b"); try t.expectEqual("123b", try sb.stringZ()); }}
test "StringBuilder.Pool: StringBuilder" { var p = try Pool.init(t.io, t.allocator, 1, 10); defer p.deinit();
// This test is testing that a single StringBuilder's lifecycle through // multiple acquire/release. In order to make sure that's what this code is // actually testing, we need to make sure that we're always dealing with the // same string builder. var prev = p.acquire() catch unreachable(); p.release(prev);
var buf: [500]u8 = undefined;
for (0..20) |_| { const sb = p.acquire() catch unreachable; defer p.release(sb);
// for the integrity of this test, make sure we're always touching the same // StringBuilder try t.expectEqual(sb, prev); prev = sb;
// fits in static try sb.write("01234"); try t.expectEqual("01234", sb.string());
// fits in static try sb.write("56789"); try t.expectEqual("0123456789", sb.string());
// requires dynamic allocation try sb.write("abcde"); try t.expectEqual("0123456789abcde", sb.string());
try sb.write("0123456789abcde"); try t.expectEqual("0123456789abcde0123456789abcde", sb.string());
const bytes = t.Random.fillAtLeast(&buf, 1); try sb.write(bytes);
const str = sb.string(); try t.expectEqual("0123456789abcde0123456789abcde", str[0..30]); try t.expectEqual(bytes, str[30..]); }}
test "StringBuilder.Pool: acquire and release" { var p = try Pool.init(t.io, t.allocator, 2, 100); defer p.deinit();
const sb1a = p.acquire() catch unreachable; const sb2a = p.acquire() catch unreachable; const sb3a = p.acquire() catch unreachable; // this should be dynamically generated
try t.expectEqual(false, sb1a == sb2a); try t.expectEqual(false, sb2a == sb3a);
sb1a.release();
const sb1b = p.acquire() catch unreachable; try t.expectEqual(true, sb1a == sb1b);
sb3a.release(); sb2a.release(); sb1b.release();}
test "StringBuilder.Pool: threadsafety" { var p = try Pool.init(t.io, t.allocator, 3, 20); defer p.deinit();
// initialize this to 0 since we're asserting that it's 0 for (p.builders) |sb| { sb.static[0] = 0; }
const t1 = try std.Thread.spawn(.{}, testPool, .{ p, 1 }); const t2 = try std.Thread.spawn(.{}, testPool, .{ p, 2 }); const t3 = try std.Thread.spawn(.{}, testPool, .{ p, 3 });
t1.join(); t2.join(); t3.join();}
fn testPool(p: *Pool, i: u8) !void { for (0..1000) |_| { var sb = p.acquire() catch unreachable; // no other thread should have changed this std.debug.assert(sb.static[0] == 0);
sb.static[0] = i; try std.Io.sleep(t.io, .fromMilliseconds(t.Random.intRange(u32, 1, 10)), .real); // no other thread should have set this to 0 std.debug.assert(sb.static[0] == i); sb.static[0] = 0; p.release(sb); }}