Commit c072cf2bb8

Andrew Kelley <andrew@ziglang.org>
2025-07-10 01:59:34
std.io.Reader.peekDelimiterInclusive: simplify and fix
1 parent 93ac765
Changed files (2)
lib/std/io/Reader.zig
@@ -752,6 +752,11 @@ pub fn peekDelimiterInclusive(r: *Reader, delimiter: u8) DelimiterError![]u8 {
         @branchHint(.likely);
         return buffer[seek .. end + 1];
     }
+    if (r.vtable.stream == &endingStream) {
+        // Protect the `@constCast` of `fixed`.
+        return error.EndOfStream;
+    }
+    r.rebase();
     while (r.buffer.len - r.end != 0) {
         const end_cap = r.buffer[r.end..];
         var writer: Writer = .fixed(end_cap);
@@ -761,29 +766,9 @@ pub fn peekDelimiterInclusive(r: *Reader, delimiter: u8) DelimiterError![]u8 {
         };
         r.end += n;
         if (std.mem.indexOfScalarPos(u8, end_cap[0..n], 0, delimiter)) |end| {
-            return r.buffer[seek .. r.end - n + end + 1];
+            return r.buffer[0 .. r.end - n + end + 1];
         }
     }
-    var i: usize = 0;
-    while (seek - i != 0) {
-        const begin_cap = r.buffer[i..seek];
-        var writer: Writer = .fixed(begin_cap);
-        const n = r.vtable.stream(r, &writer, .limited(begin_cap.len)) catch |err| switch (err) {
-            error.WriteFailed => unreachable,
-            else => |e| return e,
-        };
-        i += n;
-        if (std.mem.indexOfScalarPos(u8, r.buffer[0..seek], i, delimiter)) |end| {
-            std.mem.rotate(u8, r.buffer, seek);
-            r.seek = 0;
-            r.end += i;
-            return r.buffer[0 .. seek + end + 1];
-        }
-    } else if (i != 0) {
-        std.mem.rotate(u8, r.buffer, seek);
-        r.seek = 0;
-        r.end += i;
-    }
     return error.StreamTooLong;
 }
 
@@ -1665,6 +1650,23 @@ test "readAlloc when the backing reader provides one byte at a time" {
     try std.testing.expectEqualStrings(str, res);
 }
 
+test "takeDelimiterInclusive when it rebases" {
+    const written_line = "ABCDEFGHIJKLMNOPQRSTUVWXYZ\n";
+    var buffer: [128]u8 = undefined;
+    var tr: std.testing.Reader = .init(&buffer, &.{
+        .{ .buffer = written_line },
+        .{ .buffer = written_line },
+        .{ .buffer = written_line },
+        .{ .buffer = written_line },
+        .{ .buffer = written_line },
+        .{ .buffer = written_line },
+    });
+    const r = &tr.interface;
+    for (0..6) |_| {
+        try std.testing.expectEqualStrings(written_line, try r.takeDelimiterInclusive('\n'));
+    }
+}
+
 /// Provides a `Reader` implementation by passing data from an underlying
 /// reader through `Hasher.update`.
 ///
lib/std/testing.zig
@@ -1206,3 +1206,43 @@ pub inline fn fuzz(
 ) anyerror!void {
     return @import("root").fuzz(context, testOne, options);
 }
+
+/// A `std.io.Reader` that writes a predetermined list of buffers during `stream`.
+pub const Reader = struct {
+    calls: []const Call,
+    interface: std.io.Reader,
+    next_call_index: usize,
+    next_offset: usize,
+
+    pub const Call = struct {
+        buffer: []const u8,
+    };
+
+    pub fn init(buffer: []u8, calls: []const Call) Reader {
+        return .{
+            .next_call_index = 0,
+            .next_offset = 0,
+            .interface = .{
+                .vtable = &.{ .stream = stream },
+                .buffer = buffer,
+                .seek = 0,
+                .end = 0,
+            },
+            .calls = calls,
+        };
+    }
+
+    fn stream(io_r: *std.io.Reader, w: *std.io.Writer, limit: std.io.Limit) std.io.Reader.StreamError!usize {
+        const r: *Reader = @alignCast(@fieldParentPtr("interface", io_r));
+        if (r.calls.len - r.next_call_index == 0) return error.EndOfStream;
+        const call = r.calls[r.next_call_index];
+        const buffer = limit.sliceConst(call.buffer[r.next_offset..]);
+        const n = try w.write(buffer);
+        r.next_offset += n;
+        if (call.buffer.len - r.next_offset == 0) {
+            r.next_call_index += 1;
+            r.next_offset = 0;
+        }
+        return n;
+    }
+};