Commit d03d273117

Andrew Kelley <andrew@ziglang.org>
2025-07-01 00:37:00
std.io.Reader: fix fill implementation
now it avoids writing to buffer in the case of fixed
1 parent 1e1f026
Changed files (1)
lib
std
lib/std/io/Reader.zig
@@ -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 {