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 {...@@ -846,37 +846,44 @@ pub fn peekDelimiterExclusive(r: *Reader, delimiter: u8) DelimiterError![]u8 {
846/// Appends to `w` contents by reading from the stream until `delimiter` is846/// Appends to `w` contents by reading from the stream until `delimiter` is
847/// found. Does not write the delimiter itself.847/// found. Does not write the delimiter itself.
848///848///
849/// Returns number of bytes streamed.849/// Returns number of bytes streamed, which may be zero, or error.EndOfStream
850pub fn readDelimiter(r: *Reader, w: *Writer, delimiter: u8) StreamError!usize {850/// if the delimiter was not found.
851 const amount, const to = try r.readAny(w, delimiter, .unlimited);851///
852 return switch (to) {852/// See also:
853 .delimiter => amount,853/// * `streamDelimiterEnding`
854 .limit => unreachable,854/// * `streamDelimiterLimit`
855 .end => error.EndOfStream,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,
856 };859 };
860 if (r.seek == r.end) return error.EndOfStream;
861 return n;
857}862}
858863
859/// Appends to `w` contents by reading from the stream until `delimiter` is found.864/// Appends to `w` contents by reading from the stream until `delimiter` is found.
860/// Does not write the delimiter itself.865/// Does not write the delimiter itself.
861///866///
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.
863///869///
864/// Returns number of bytes streamed. The end is not signaled to the writer.870/// See also:
865pub fn readDelimiterEnding(871/// * `streamDelimiter`
872/// * `streamDelimiterLimit`
873pub fn streamDelimiterEnding(
866 r: *Reader,874 r: *Reader,
867 w: *Writer,875 w: *Writer,
868 delimiter: u8,876 delimiter: u8,
869) StreamRemainingError!usize {877) StreamRemainingError!usize {
870 const amount, const to = try r.readAny(w, delimiter, .unlimited);878 return streamDelimiterLimit(r, w, delimiter, .unlimited) catch |err| switch (err) {
871 return switch (to) {879 error.StreamTooLong => unreachable, // unlimited is passed
872 .delimiter, .end => amount,880 else => |e| return e,
873 .limit => unreachable,
874 };881 };
875}882}
876883
877pub const StreamDelimiterLimitedError = StreamRemainingError || error{884pub const StreamDelimiterLimitError = error{
878 /// Stream ended before the delimiter was found.885 ReadFailed,
879 EndOfStream,886 WriteFailed,
880 /// The delimiter was not found within the limit.887 /// The delimiter was not found within the limit.
881 StreamTooLong,888 StreamTooLong,
882};889};
...@@ -884,45 +891,31 @@ pub const StreamDelimiterLimitedError = StreamRemainingError || error{...@@ -884,45 +891,31 @@ pub const StreamDelimiterLimitedError = StreamRemainingError || error{
884/// Appends to `w` contents by reading from the stream until `delimiter` is found.891/// Appends to `w` contents by reading from the stream until `delimiter` is found.
885/// Does not write the delimiter itself.892/// Does not write the delimiter itself.
886///893///
887/// Returns number of bytes streamed.894/// Returns number of bytes streamed, which may be zero. End of stream can be
888pub fn readDelimiterLimit(895/// detected by checking if the next byte in the stream is the delimiter.
896pub fn streamDelimiterLimit(
889 r: *Reader,897 r: *Reader,
890 w: *Writer,898 w: *Writer,
891 delimiter: u8,899 delimiter: u8,
892 limit: Limit,900 limit: Limit,
893) StreamDelimiterLimitedError!usize {901) StreamDelimiterLimitError!usize {
894 const amount, const to = try r.readAny(w, delimiter, limit);902 var remaining = @intFromEnum(limit);
895 return switch (to) {903 while (remaining != 0) {
896 .delimiter => amount,904 const available = Limit.limited(remaining).slice(r.peekGreedy(1) catch |err| switch (err) {
897 .limit => error.StreamTooLong,905 error.ReadFailed => return error.ReadFailed,
898 .end => error.EndOfStream,906 error.EndOfStream => return @intFromEnum(limit) - remaining,
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 },
914 });907 });
915 if (delimiter) |d| if (std.mem.indexOfScalar(u8, available, d)) |delimiter_index| {908 if (std.mem.indexOfScalar(u8, available, delimiter)) |delimiter_index| {
916 try w.writeAll(available[0..delimiter_index]);909 try w.writeAll(available[0..delimiter_index]);
917 r.toss(delimiter_index + 1);910 r.toss(delimiter_index);
918 return .{ amount + delimiter_index, .delimiter };911 remaining -= delimiter_index;
919 };912 return @intFromEnum(limit) - remaining;
913 }
920 try w.writeAll(available);914 try w.writeAll(available);
921 r.toss(available.len);915 r.toss(available.len);
922 amount += available.len;916 remaining -= available.len;
923 remaining = remaining.subtract(available.len).?;
924 }917 }
925 return .{ amount, .limit };918 return error.StreamTooLong;
926}919}
927920
928/// Reads from the stream until specified byte is found, discarding all data,921/// Reads from the stream until specified byte is found, discarding all data,
...@@ -1348,15 +1341,33 @@ test peekDelimiterExclusive {...@@ -1348,15 +1341,33 @@ test peekDelimiterExclusive {
1348 try testing.expectEqualStrings("c", try r.peekDelimiterExclusive('\n'));1341 try testing.expectEqualStrings("c", try r.peekDelimiterExclusive('\n'));
1349}1342}
13501343
1351test readDelimiter {1344test streamDelimiter {
1352 return error.Unimplemented;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'));
1353}1354}
13541355
1355test readDelimiterEnding {1356test streamDelimiterEnding {
1356 return error.Unimplemented;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'));
1357}1368}
13581369
1359test readDelimiterLimit {1370test streamDelimiterLimit {
1360 return error.Unimplemented;1371 return error.Unimplemented;
1361}1372}
13621373