mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 13:54:21 +00:00
These functions are currently footgunny when working with pointers to arrays and slices. They just return the stated length of the array/slice without iterating and looking for the first sentinel, even if the array/slice is a sentinel terminated type. From looking at the quite small list of places in the standard library/compiler that this change breaks existing code, the new code looks to be more readable in all cases. The usage of std.mem.span/len was totally unneeded in most of the cases affected by this breaking change. We could remove these functions entirely in favor of other existing functions in std.mem such as std.mem.sliceTo(), but that would be a somewhat nasty breaking change as std.mem.span() is very widely used for converting sentinel terminated pointers to slices. It is however not at all widely used for anything else. Therefore I think it is better to break these few non-standard and potentially incorrect usages of these functions now and at some later time, if deemed worthwhile, finally remove these functions. If we wait for at least a full release cycle so that everyone adapts to this change first, updating for the removal could be a simple find and replace without needing to worry about the semantics.
202 lines
6.5 KiB
Zig
202 lines
6.5 KiB
Zig
const std = @import("../std.zig");
|
|
const io = std.io;
|
|
const testing = std.testing;
|
|
const mem = std.mem;
|
|
const assert = std.debug.assert;
|
|
|
|
/// This turns a byte buffer into an `io.Writer`, `io.Reader`, or `io.SeekableStream`.
|
|
/// If the supplied byte buffer is const, then `io.Writer` is not available.
|
|
pub fn FixedBufferStream(comptime Buffer: type) type {
|
|
return struct {
|
|
/// `Buffer` is either a `[]u8` or `[]const u8`.
|
|
buffer: Buffer,
|
|
pos: usize,
|
|
|
|
pub const ReadError = error{};
|
|
pub const WriteError = error{NoSpaceLeft};
|
|
pub const SeekError = error{};
|
|
pub const GetSeekPosError = error{};
|
|
|
|
pub const Reader = io.Reader(*Self, ReadError, read);
|
|
pub const Writer = io.Writer(*Self, WriteError, write);
|
|
|
|
pub const SeekableStream = io.SeekableStream(
|
|
*Self,
|
|
SeekError,
|
|
GetSeekPosError,
|
|
seekTo,
|
|
seekBy,
|
|
getPos,
|
|
getEndPos,
|
|
);
|
|
|
|
const Self = @This();
|
|
|
|
pub fn reader(self: *Self) Reader {
|
|
return .{ .context = self };
|
|
}
|
|
|
|
pub fn writer(self: *Self) Writer {
|
|
return .{ .context = self };
|
|
}
|
|
|
|
pub fn seekableStream(self: *Self) SeekableStream {
|
|
return .{ .context = self };
|
|
}
|
|
|
|
pub fn read(self: *Self, dest: []u8) ReadError!usize {
|
|
const size = std.math.min(dest.len, self.buffer.len - self.pos);
|
|
const end = self.pos + size;
|
|
|
|
mem.copy(u8, dest[0..size], self.buffer[self.pos..end]);
|
|
self.pos = end;
|
|
|
|
return size;
|
|
}
|
|
|
|
/// If the returned number of bytes written is less than requested, the
|
|
/// buffer is full. Returns `error.NoSpaceLeft` when no bytes would be written.
|
|
/// Note: `error.NoSpaceLeft` matches the corresponding error from
|
|
/// `std.fs.File.WriteError`.
|
|
pub fn write(self: *Self, bytes: []const u8) WriteError!usize {
|
|
if (bytes.len == 0) return 0;
|
|
if (self.pos >= self.buffer.len) return error.NoSpaceLeft;
|
|
|
|
const n = if (self.pos + bytes.len <= self.buffer.len)
|
|
bytes.len
|
|
else
|
|
self.buffer.len - self.pos;
|
|
|
|
mem.copy(u8, self.buffer[self.pos .. self.pos + n], bytes[0..n]);
|
|
self.pos += n;
|
|
|
|
if (n == 0) return error.NoSpaceLeft;
|
|
|
|
return n;
|
|
}
|
|
|
|
pub fn seekTo(self: *Self, pos: u64) SeekError!void {
|
|
self.pos = if (std.math.cast(usize, pos)) |x| std.math.min(self.buffer.len, x) else self.buffer.len;
|
|
}
|
|
|
|
pub fn seekBy(self: *Self, amt: i64) SeekError!void {
|
|
if (amt < 0) {
|
|
const abs_amt = std.math.absCast(amt);
|
|
const abs_amt_usize = std.math.cast(usize, abs_amt) orelse std.math.maxInt(usize);
|
|
if (abs_amt_usize > self.pos) {
|
|
self.pos = 0;
|
|
} else {
|
|
self.pos -= abs_amt_usize;
|
|
}
|
|
} else {
|
|
const amt_usize = std.math.cast(usize, amt) orelse std.math.maxInt(usize);
|
|
const new_pos = std.math.add(usize, self.pos, amt_usize) catch std.math.maxInt(usize);
|
|
self.pos = std.math.min(self.buffer.len, new_pos);
|
|
}
|
|
}
|
|
|
|
pub fn getEndPos(self: *Self) GetSeekPosError!u64 {
|
|
return self.buffer.len;
|
|
}
|
|
|
|
pub fn getPos(self: *Self) GetSeekPosError!u64 {
|
|
return self.pos;
|
|
}
|
|
|
|
pub fn getWritten(self: Self) Buffer {
|
|
return self.buffer[0..self.pos];
|
|
}
|
|
|
|
pub fn reset(self: *Self) void {
|
|
self.pos = 0;
|
|
}
|
|
};
|
|
}
|
|
|
|
pub fn fixedBufferStream(buffer: anytype) FixedBufferStream(Slice(@TypeOf(buffer))) {
|
|
return .{ .buffer = buffer, .pos = 0 };
|
|
}
|
|
|
|
fn Slice(comptime T: type) type {
|
|
switch (@typeInfo(T)) {
|
|
.Pointer => |ptr_info| {
|
|
var new_ptr_info = ptr_info;
|
|
switch (ptr_info.size) {
|
|
.Slice => {},
|
|
.One => switch (@typeInfo(ptr_info.child)) {
|
|
.Array => |info| new_ptr_info.child = info.child,
|
|
else => @compileError("invalid type given to fixedBufferStream"),
|
|
},
|
|
else => @compileError("invalid type given to fixedBufferStream"),
|
|
}
|
|
new_ptr_info.size = .Slice;
|
|
return @Type(.{ .Pointer = new_ptr_info });
|
|
},
|
|
else => @compileError("invalid type given to fixedBufferStream"),
|
|
}
|
|
}
|
|
|
|
test "FixedBufferStream output" {
|
|
var buf: [255]u8 = undefined;
|
|
var fbs = fixedBufferStream(&buf);
|
|
const stream = fbs.writer();
|
|
|
|
try stream.print("{s}{s}!", .{ "Hello", "World" });
|
|
try testing.expectEqualSlices(u8, "HelloWorld!", fbs.getWritten());
|
|
}
|
|
|
|
test "FixedBufferStream output at comptime" {
|
|
comptime {
|
|
var buf: [255]u8 = undefined;
|
|
var fbs = fixedBufferStream(&buf);
|
|
const stream = fbs.writer();
|
|
|
|
try stream.print("{s}{s}!", .{ "Hello", "World" });
|
|
try testing.expectEqualSlices(u8, "HelloWorld!", fbs.getWritten());
|
|
}
|
|
}
|
|
|
|
test "FixedBufferStream output 2" {
|
|
var buffer: [10]u8 = undefined;
|
|
var fbs = fixedBufferStream(&buffer);
|
|
|
|
try fbs.writer().writeAll("Hello");
|
|
try testing.expect(mem.eql(u8, fbs.getWritten(), "Hello"));
|
|
|
|
try fbs.writer().writeAll("world");
|
|
try testing.expect(mem.eql(u8, fbs.getWritten(), "Helloworld"));
|
|
|
|
try testing.expectError(error.NoSpaceLeft, fbs.writer().writeAll("!"));
|
|
try testing.expect(mem.eql(u8, fbs.getWritten(), "Helloworld"));
|
|
|
|
fbs.reset();
|
|
try testing.expect(fbs.getWritten().len == 0);
|
|
|
|
try testing.expectError(error.NoSpaceLeft, fbs.writer().writeAll("Hello world!"));
|
|
try testing.expect(mem.eql(u8, fbs.getWritten(), "Hello worl"));
|
|
|
|
try fbs.seekTo((try fbs.getEndPos()) + 1);
|
|
try testing.expectError(error.NoSpaceLeft, fbs.writer().writeAll("H"));
|
|
}
|
|
|
|
test "FixedBufferStream input" {
|
|
const bytes = [_]u8{ 1, 2, 3, 4, 5, 6, 7 };
|
|
var fbs = fixedBufferStream(&bytes);
|
|
|
|
var dest: [4]u8 = undefined;
|
|
|
|
var read = try fbs.reader().read(&dest);
|
|
try testing.expect(read == 4);
|
|
try testing.expect(mem.eql(u8, dest[0..4], bytes[0..4]));
|
|
|
|
read = try fbs.reader().read(&dest);
|
|
try testing.expect(read == 3);
|
|
try testing.expect(mem.eql(u8, dest[0..3], bytes[4..7]));
|
|
|
|
read = try fbs.reader().read(&dest);
|
|
try testing.expect(read == 0);
|
|
|
|
try fbs.seekTo((try fbs.getEndPos()) + 1);
|
|
read = try fbs.reader().read(&dest);
|
|
try testing.expect(read == 0);
|
|
}
|