std.io.Reader: fix fill implementation

now it avoids writing to buffer in the case of fixed
This commit is contained in:
Andrew Kelley 2025-06-30 15:37:00 -07:00
parent 1e1f026c83
commit d03d273117

View File

@ -106,7 +106,7 @@ const ending_state: Reader = .fixed(&.{});
pub const ending: *Reader = @constCast(&ending_state);
pub fn limited(r: *Reader, limit: Limit, buffer: []u8) Limited {
return Limited.init(r, limit, buffer);
return .init(r, limit, buffer);
}
/// Constructs a `Reader` such that it will read from `buffer` and then end.
@ -921,21 +921,70 @@ pub fn streamDelimiterLimit(
/// Reads from the stream until specified byte is found, discarding all data,
/// including the delimiter.
///
/// If end of stream is found, this function succeeds.
pub fn discardDelimiterInclusive(r: *Reader, delimiter: u8) Error!void {
_ = r;
_ = delimiter;
@panic("TODO");
/// Returns number of bytes discarded, or `error.EndOfStream` if the delimiter
/// is not found.
///
/// See also:
/// * `discardDelimiterExclusive`
/// * `discardDelimiterLimit`
pub fn discardDelimiterInclusive(r: *Reader, delimiter: u8) Error!usize {
const n = discardDelimiterLimit(r, delimiter, .unlimited) catch |err| switch (err) {
error.StreamTooLong => unreachable, // unlimited is passed
else => |e| return e,
};
if (r.seek == r.end) return error.EndOfStream;
assert(r.buffer[r.seek] == delimiter);
toss(r, 1);
return n + 1;
}
/// Reads from the stream until specified byte is found, discarding all data,
/// excluding the delimiter.
///
/// Succeeds if stream ends before delimiter found.
pub fn discardDelimiterExclusive(r: *Reader, delimiter: u8) ShortError!void {
_ = r;
_ = delimiter;
@panic("TODO");
/// Returns the number of bytes discarded.
///
/// Succeeds if stream ends before delimiter found. End of stream can be
/// detected by checking if the delimiter is buffered.
///
/// See also:
/// * `discardDelimiterInclusive`
/// * `discardDelimiterLimit`
pub fn discardDelimiterExclusive(r: *Reader, delimiter: u8) ShortError!usize {
return discardDelimiterLimit(r, delimiter, .unlimited) catch |err| switch (err) {
error.StreamTooLong => unreachable, // unlimited is passed
else => |e| return e,
};
}
pub const DiscardDelimiterLimitError = error{
ReadFailed,
/// The delimiter was not found within the limit.
StreamTooLong,
};
/// Reads from the stream until specified byte is found, discarding all data,
/// excluding the delimiter.
///
/// Returns the number of bytes discarded.
///
/// Succeeds if stream ends before delimiter found. End of stream can be
/// detected by checking if the delimiter is buffered.
pub fn discardDelimiterLimit(r: *Reader, delimiter: u8, limit: Limit) DiscardDelimiterLimitError!usize {
var remaining = @intFromEnum(limit);
while (remaining != 0) {
const available = Limit.limited(remaining).slice(r.peekGreedy(1) catch |err| switch (err) {
error.ReadFailed => return error.ReadFailed,
error.EndOfStream => return @intFromEnum(limit) - remaining,
});
if (std.mem.indexOfScalar(u8, available, delimiter)) |delimiter_index| {
r.toss(delimiter_index);
remaining -= delimiter_index;
return @intFromEnum(limit) - remaining;
}
r.toss(available.len);
remaining -= available.len;
}
return error.StreamTooLong;
}
/// Fills the buffer such that it contains at least `n` bytes, without
@ -951,6 +1000,19 @@ pub fn fill(r: *Reader, n: usize) Error!void {
@branchHint(.likely);
return;
}
if (r.seek + n <= r.buffer.len) while (true) {
const end_cap = r.buffer[r.end..];
var writer: Writer = .fixed(end_cap);
r.end += r.vtable.stream(r, &writer, .limited(end_cap.len)) catch |err| switch (err) {
error.WriteFailed => unreachable,
else => |e| return e,
};
if (r.seek + n <= r.end) return;
};
if (r.vtable.stream == &endingStream) {
// Protect the `@constCast` of `fixed`.
return error.EndOfStream;
}
rebaseCapacity(r, n);
var writer: Writer = .{
.buffer = r.buffer,
@ -1371,37 +1433,65 @@ test streamDelimiterLimit {
var w: Writer = .fixed(&out_buffer);
try testing.expectError(error.StreamTooLong, r.streamDelimiterLimit(&w, '\n', .limited(2)));
try testing.expectEqual(1, try r.streamDelimiterLimit(&w, '\n', .limited(3)));
r.toss(1);
try testing.expectEqualStrings("\n", try r.take(1));
try testing.expectEqual(4, try r.streamDelimiterLimit(&w, '\n', .unlimited));
try testing.expectEqualStrings("foobars", w.buffered());
}
test discardDelimiterExclusive {
return error.Unimplemented;
var r: Reader = .fixed("foob\nar");
try testing.expectEqual(4, try r.discardDelimiterExclusive('\n'));
try testing.expectEqualStrings("\n", try r.take(1));
try testing.expectEqual(2, try r.discardDelimiterExclusive('\n'));
try testing.expectEqual(0, try r.discardDelimiterExclusive('\n'));
}
test discardDelimiterInclusive {
return error.Unimplemented;
var r: Reader = .fixed("foob\nar");
try testing.expectEqual(5, try r.discardDelimiterInclusive('\n'));
try testing.expectError(error.EndOfStream, r.discardDelimiterInclusive('\n'));
}
test discardDelimiterLimit {
var r: Reader = .fixed("foob\nar");
try testing.expectError(error.StreamTooLong, r.discardDelimiterLimit('\n', .limited(4)));
try testing.expectEqual(0, try r.discardDelimiterLimit('\n', .limited(2)));
try testing.expectEqualStrings("\n", try r.take(1));
try testing.expectEqual(2, try r.discardDelimiterLimit('\n', .unlimited));
try testing.expectEqual(0, try r.discardDelimiterLimit('\n', .unlimited));
}
test fill {
return error.Unimplemented;
var r: Reader = .fixed("abc");
try r.fill(1);
try r.fill(3);
}
test takeByte {
return error.Unimplemented;
var r: Reader = .fixed("ab");
try testing.expectEqual('a', try r.takeByte());
try testing.expectEqual('b', try r.takeByte());
try testing.expectError(error.EndOfStream, r.takeByte());
}
test takeByteSigned {
return error.Unimplemented;
var r: Reader = .fixed(&.{ 255, 5 });
try testing.expectEqual(-1, try r.takeByteSigned());
try testing.expectEqual(5, try r.takeByteSigned());
try testing.expectError(error.EndOfStream, r.takeByteSigned());
}
test takeInt {
return error.Unimplemented;
var r: Reader = .fixed(&.{ 0x12, 0x34, 0x56 });
try testing.expectEqual(0x1234, try r.takeInt(u16, .big));
try testing.expectError(error.EndOfStream, r.takeInt(u16, .little));
}
test takeVarInt {
return error.Unimplemented;
var r: Reader = .fixed(&.{ 0x12, 0x34, 0x56 });
std.debug.print("{x}", .{r.buffer});
try testing.expectEqual(0x123456, try r.takeVarInt(u64, .big, 3));
try testing.expectError(error.EndOfStream, r.takeVarInt(u16, .little, 1));
}
test takeStruct {