// ErrCtx is a way to pass friendly error messages to the user. // Preallocates memory. Discards messages that do not fit. const std = @import("std"); const mem = std.mem; const math = std.math; const BoundedArray = std.BoundedArray; const assert = std.debug.assert; const capacity = 1 << 16; // 64K const ErrCtx = @This(); buf: BoundedArray(u8, capacity) = BoundedArray(u8, capacity).init(0) catch unreachable, overflow: bool = false, dirty: bool = false, pub fn write(self: *ErrCtx, bytes: []const u8) error{}!usize { self.dirty = true; const can_add = capacity - self.buf.len; if (can_add == 0) return bytes.len; self.overflow = bytes.len > can_add; self.buf.appendSliceAssumeCapacity(bytes[0..math.min(bytes.len, can_add)]); // not adding the final zero is ok, because it will // be ignored in the iterator anyway. _ = self.buf.append(0) catch null; return bytes.len; } const Writer = std.io.Writer(*ErrCtx, error{}, write); // writer is private, because fmt.print(...) can do multiple write(...) calls // for a single fmt.print(...). It is too easy to mis-use; therefore use the // wrappers below. fn writer(self: *ErrCtx) Writer { return Writer{ .context = self }; } pub fn print(self: *ErrCtx, comptime format: []const u8, args: anytype) void { var buf: [capacity]u8 = undefined; var buf_stream = std.io.fixedBufferStream(&buf); buf_stream.writer().print(format, args) catch |err| switch (err) { error.NoSpaceLeft => {}, }; _ = self.write(buf[0..buf_stream.pos]) catch unreachable; } pub fn returnf( self: *ErrCtx, comptime format: []const u8, args: anytype, comptime ret: anytype, ) @TypeOf(ret) { self.print(format, args); return ret; } pub fn wrap(self: *ErrCtx, comptime msg: []const u8) *ErrCtx { self.writer().writeAll(msg) catch unreachable; return self; } pub fn wrapf(self: *ErrCtx, comptime format: []const u8, args: anytype) *ErrCtx { self.print(format, args); return self; } pub fn iterator(self: *const ErrCtx) mem.SplitIterator(u8) { const slice = self.buf.constSlice(); const last_byte = if (slice[slice.len - 1] == 0) slice.len - 1 else slice.len; return mem.split(u8, slice[0..last_byte], "\x00"); } pub fn rev(self: *const ErrCtx) mem.SplitBackwardsIterator(u8) { const slice = self.buf.constSlice(); if (slice.len == 0) { return mem.SplitBackwardsIterator(u8){ .buffer = slice, .index = null, .delimiter = "\x00", }; } const last_byte = if (slice[slice.len - 1] == 0) (slice.len - 1) else slice.len; return mem.splitBackwards(u8, slice[0..last_byte], "\x00"); } pub fn unwrap(self: *const ErrCtx) BoundedArray(u8, capacity * 2) { var result = BoundedArray(u8, capacity * 2).init(0) catch unreachable; var wr = result.writer(); var it = self.rev(); if (it.next()) |msg| { wr.print("{s}", .{msg}) catch unreachable; } else return result; while (it.next()) |msg| wr.print(": {s}", .{msg}) catch unreachable; return result; } const testing = std.testing; test "basics" { var ctx = ErrCtx{}; var wr = ctx.writer(); try wr.writeAll("0" ** 10); try wr.writeAll("1" ** 10); try wr.writeAll("3" ** capacity); try wr.writeAll("foo"); var it = ctx.iterator(); try testing.expectEqualSlices(u8, it.next().?, "0" ** 10); try testing.expectEqualSlices(u8, it.next().?, "1" ** 10); const long = it.next().?; try testing.expectEqual(@as(usize, capacity - 2 - 20), long.len); try testing.expectEqual(it.next(), null); try testing.expect(ctx.overflow); var it_rev = ctx.rev(); try testing.expectEqualSlices(u8, it_rev.next().?, "3" ** (capacity - 22)); try testing.expectEqualSlices(u8, it_rev.next().?, "1" ** 10); try testing.expectEqualSlices(u8, it_rev.next().?, "0" ** 10); } test "almost overflow" { var ctx = ErrCtx{}; var wr = ctx.writer(); try wr.writeAll("0" ** (capacity - 3)); try wr.writeAll("11"); var it = ctx.iterator(); try testing.expectEqualSlices(u8, it.next().?, "0" ** (capacity - 3)); try testing.expectEqualSlices(u8, it.next().?, "11"); try testing.expectEqual(it.next(), null); var it_rev = ctx.rev(); try testing.expectEqualSlices(u8, it_rev.next().?, "11"); try testing.expectEqualSlices(u8, it_rev.next().?, "0" ** (capacity - 3)); try testing.expectEqual(it.next(), null); } test "rev" { var ctx = ErrCtx{}; ctx.print("yadda {s}", .{"xx"}); var it = ctx.rev(); try testing.expectEqualStrings("yadda xx", it.next().?); try testing.expectEqual(it.next(), null); }