authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-06-30 10:58:42-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-07 22:43:51-07:00
log77e839e28326d635e6a00513e26bfaf0076f22ec
tree26c47a4dc21fde5081361d80f516098ac099ae68
parent1d763c8b29360a5f3b52747231876b54db1c7ba0

std.io.Reader: fix streamDelimiter and streamDelimiterEnding


1 files changed, 63 insertions(+), 52 deletions(-)

lib/std/io/Reader.zig+63-52
......@@ -846,37 +846,44 @@ pub fn peekDelimiterExclusive(r: *Reader, delimiter: u8) DelimiterError![]u8 {
846846/// Appends to `w` contents by reading from the stream until `delimiter` is
847847/// found. Does not write the delimiter itself.
848848///
849/// Returns number of bytes streamed.
850pub fn readDelimiter(r: *Reader, w: *Writer, delimiter: u8) StreamError!usize {
851 const amount, const to = try r.readAny(w, delimiter, .unlimited);
852 return switch (to) {
853 .delimiter => amount,
854 .limit => unreachable,
855 .end => error.EndOfStream,
849/// Returns number of bytes streamed, which may be zero, or error.EndOfStream
850/// if the delimiter was not found.
851///
852/// See also:
853/// * `streamDelimiterEnding`
854/// * `streamDelimiterLimit`
855pub fn streamDelimiter(r: *Reader, w: *Writer, delimiter: u8) StreamError!usize {
856 const n = streamDelimiterLimit(r, w, delimiter, .unlimited) catch |err| switch (err) {
857 error.StreamTooLong => unreachable, // unlimited is passed
858 else => |e| return e,
856859 };
860 if (r.seek == r.end) return error.EndOfStream;
861 return n;
857862}
858863
859864/// Appends to `w` contents by reading from the stream until `delimiter` is found.
860865/// Does not write the delimiter itself.
861866///
862/// Succeeds if stream ends before delimiter found.
867/// Returns number of bytes streamed, which may be zero. End of stream can be
868/// detected by checking if the next byte in the stream is the delimiter.
863869///
864/// Returns number of bytes streamed. The end is not signaled to the writer.
865pub fn readDelimiterEnding(
870/// See also:
871/// * `streamDelimiter`
872/// * `streamDelimiterLimit`
873pub fn streamDelimiterEnding(
866874 r: *Reader,
867875 w: *Writer,
868876 delimiter: u8,
869877) StreamRemainingError!usize {
870 const amount, const to = try r.readAny(w, delimiter, .unlimited);
871 return switch (to) {
872 .delimiter, .end => amount,
873 .limit => unreachable,
878 return streamDelimiterLimit(r, w, delimiter, .unlimited) catch |err| switch (err) {
879 error.StreamTooLong => unreachable, // unlimited is passed
880 else => |e| return e,
874881 };
875882}
876883
877pub const StreamDelimiterLimitedError = StreamRemainingError || error{
878 /// Stream ended before the delimiter was found.
879 EndOfStream,
884pub const StreamDelimiterLimitError = error{
885 ReadFailed,
886 WriteFailed,
880887 /// The delimiter was not found within the limit.
881888 StreamTooLong,
882889};
......@@ -884,45 +891,31 @@ pub const StreamDelimiterLimitedError = StreamRemainingError || error{
884891/// Appends to `w` contents by reading from the stream until `delimiter` is found.
885892/// Does not write the delimiter itself.
886893///
887/// Returns number of bytes streamed.
888pub fn readDelimiterLimit(
894/// Returns number of bytes streamed, which may be zero. End of stream can be
895/// detected by checking if the next byte in the stream is the delimiter.
896pub fn streamDelimiterLimit(
889897 r: *Reader,
890898 w: *Writer,
891899 delimiter: u8,
892900 limit: Limit,
893) StreamDelimiterLimitedError!usize {
894 const amount, const to = try r.readAny(w, delimiter, limit);
895 return switch (to) {
896 .delimiter => amount,
897 .limit => error.StreamTooLong,
898 .end => error.EndOfStream,
899 };
900}
901
902fn readAny(
903 r: *Reader,
904 w: *Writer,
905 delimiter: ?u8,
906 limit: Limit,
907) StreamRemainingError!struct { usize, enum { delimiter, limit, end } } {
908 var amount: usize = 0;
909 var remaining = limit;
910 while (remaining.nonzero()) {
911 const available = remaining.slice(r.peekGreedy(1) catch |err| switch (err) {
912 error.ReadFailed => |e| return e,
913 error.EndOfStream => return .{ amount, .end },
901) StreamDelimiterLimitError!usize {
902 var remaining = @intFromEnum(limit);
903 while (remaining != 0) {
904 const available = Limit.limited(remaining).slice(r.peekGreedy(1) catch |err| switch (err) {
905 error.ReadFailed => return error.ReadFailed,
906 error.EndOfStream => return @intFromEnum(limit) - remaining,
914907 });
915 if (delimiter) |d| if (std.mem.indexOfScalar(u8, available, d)) |delimiter_index| {
908 if (std.mem.indexOfScalar(u8, available, delimiter)) |delimiter_index| {
916909 try w.writeAll(available[0..delimiter_index]);
917 r.toss(delimiter_index + 1);
918 return .{ amount + delimiter_index, .delimiter };
919 };
910 r.toss(delimiter_index);
911 remaining -= delimiter_index;
912 return @intFromEnum(limit) - remaining;
913 }
920914 try w.writeAll(available);
921915 r.toss(available.len);
922 amount += available.len;
923 remaining = remaining.subtract(available.len).?;
916 remaining -= available.len;
924917 }
925 return .{ amount, .limit };
918 return error.StreamTooLong;
926919}
927920
928921/// Reads from the stream until specified byte is found, discarding all data,
......@@ -1348,15 +1341,33 @@ test peekDelimiterExclusive {
13481341 try testing.expectEqualStrings("c", try r.peekDelimiterExclusive('\n'));
13491342}
13501343
1351test readDelimiter {
1352 return error.Unimplemented;
1344test streamDelimiter {
1345 var out_buffer: [10]u8 = undefined;
1346 var r: Reader = .fixed("foo\nbars");
1347 var w: Writer = .fixed(&out_buffer);
1348 // Short streams are possible with this function but not with fixed.
1349 try testing.expectEqual(3, try r.streamDelimiter(&w, '\n'));
1350 try testing.expectEqualStrings("foo", w.buffered());
1351 try testing.expectEqual(0, try r.streamDelimiter(&w, '\n'));
1352 r.toss(1);
1353 try testing.expectError(error.EndOfStream, r.streamDelimiter(&w, '\n'));
13531354}
13541355
1355test readDelimiterEnding {
1356 return error.Unimplemented;
1356test streamDelimiterEnding {
1357 var out_buffer: [10]u8 = undefined;
1358 var r: Reader = .fixed("foo\nbars");
1359 var w: Writer = .fixed(&out_buffer);
1360 // Short streams are possible with this function but not with fixed.
1361 try testing.expectEqual(3, try r.streamDelimiterEnding(&w, '\n'));
1362 try testing.expectEqualStrings("foo", w.buffered());
1363 r.toss(1);
1364 try testing.expectEqual(4, try r.streamDelimiterEnding(&w, '\n'));
1365 try testing.expectEqualStrings("foobars", w.buffered());
1366 try testing.expectEqual(0, try r.streamDelimiterEnding(&w, '\n'));
1367 try testing.expectEqual(0, try r.streamDelimiterEnding(&w, '\n'));
13571368}
13581369
1359test readDelimiterLimit {
1370test streamDelimiterLimit {
13601371 return error.Unimplemented;
13611372}
13621373