Skip to content

Commit eae4193

Browse files
author
Ary Borenszweig
committed
IO#peek now returns an empty slice on EOF. Fixes #4240
1 parent c01a6c7 commit eae4193

File tree

9 files changed

+53
-28
lines changed

9 files changed

+53
-28
lines changed

spec/std/io/buffered_spec.cr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -315,8 +315,8 @@ describe "IO::Buffered" do
315315
# Peek doesn't advance
316316
io.gets_to_end.should eq("foo")
317317

318-
# Returns nil if no more data
319-
io.peek.should be_nil
318+
# Returns EOF if no more data
319+
io.peek.should eq(Bytes.empty)
320320
end
321321

322322
it "skips" do

spec/std/io/memory_spec.cr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,7 @@ describe IO::Memory do
342342
io.peek.should eq("lo world".to_slice)
343343

344344
io.skip_to_end
345-
io.peek.should be_nil
345+
io.peek.should eq(Bytes.empty)
346346
end
347347

348348
it "skips" do

spec/std/io/sized_spec.cr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ describe "IO::Sized" do
114114
sized = IO::Sized.new(io, read_size: 6)
115115
sized.peek.should eq("123456".to_slice)
116116
sized.gets_to_end.should eq("123456")
117-
sized.peek.should be_nil
117+
sized.peek.should eq(Bytes.empty)
118118
end
119119

120120
it "skips" do

src/http/content.cr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ module HTTP
102102
peek = peek[0, @chunk_remaining]
103103
end
104104

105-
return peek.empty? ? nil : peek
105+
return peek
106106
elsif @read_chunk_start
107107
read_chunk_start
108108
next

src/io.cr

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -392,35 +392,36 @@ module IO
392392
private def read_char_with_bytesize
393393
# For UTF-8 encoding, try to see if we can peek 4 bytes.
394394
# If so, this will be faster than reading byte per byte.
395-
if !decoder && (peek = self.peek) && peek.size == 4
396-
read_char_with_bytesize_peek(peek)
395+
if !decoder && (peek = self.peek)
396+
if peek.empty?
397+
return nil
398+
else
399+
return read_char_with_bytesize_peek(peek)
400+
end
397401
else
398402
read_char_with_bytesize_slow
399403
end
400404
end
401405

402406
private def read_char_with_bytesize_peek(peek)
403407
first = peek[0].to_u32
408+
skip(1)
404409
if first < 0x80
405-
skip(1)
406410
return first.unsafe_chr, 1
407411
end
408412

409-
second = (peek[1] & 0x3f).to_u32
413+
second = peek_or_read_masked(peek, 1)
410414
if first < 0xe0
411-
skip(2)
412415
return ((first & 0x1f) << 6 | second).unsafe_chr, 2
413416
end
414417

415-
third = (peek[2] & 0x3f).to_u32
418+
third = peek_or_read_masked(peek, 2)
416419
if first < 0xf0
417-
skip(3)
418420
return ((first & 0x0f) << 12 | (second << 6) | third).unsafe_chr, 3
419421
end
420422

421-
fourth = (peek[3] & 0x3f).to_u32
423+
fourth = peek_or_read_masked(peek, 3)
422424
if first < 0xf8
423-
skip(4)
424425
return ((first & 0x07) << 18 | (second << 12) | (third << 6) | fourth).unsafe_chr, 4
425426
end
426427

@@ -451,6 +452,15 @@ module IO
451452
(byte & 0x3f).to_u32
452453
end
453454

455+
private def peek_or_read_masked(peek, index)
456+
if byte = peek[index]?
457+
skip(1)
458+
(byte & 0x3f).to_u32
459+
else
460+
read_utf8_masked_byte
461+
end
462+
end
463+
454464
# Reads a single decoded UTF-8 byte from this `IO`.
455465
# Returns `nil` if there is no more data to read.
456466
#
@@ -527,9 +537,10 @@ module IO
527537

528538
# Peeks into this IO, if possible.
529539
#
530-
# If this IO can peek into some data, it returns a slice
531-
# with that data. Returns `nil` if this IO isn't peekable,
532-
# or if there's no data to peek.
540+
# It returns:
541+
# - `nil` if this IO isn't peekable
542+
# - an empty slice if it is, but EOF was reached
543+
# - a non-empty slice if some data can be peeked
533544
#
534545
# The returned bytes are only valid data until a next call
535546
# to any method that reads from this IO is invoked.
@@ -698,7 +709,11 @@ module IO
698709
# If there's no encoding, the delimiter is ASCII and we can peek,
699710
# use a faster algorithm
700711
if ascii && !decoder && (peek = self.peek)
701-
gets_peek(delimiter, limit, chomp, peek)
712+
if peek.empty?
713+
nil
714+
else
715+
gets_peek(delimiter, limit, chomp, peek)
716+
end
702717
else
703718
gets_slow(delimiter, limit, chomp)
704719
end
@@ -756,7 +771,7 @@ module IO
756771
peek = self.peek
757772
end
758773

759-
unless peek
774+
if !peek || peek.empty?
760775
if buffer.bytesize == 0
761776
return nil
762777
else
@@ -786,7 +801,7 @@ module IO
786801
buffer = String::Builder.new
787802
total = 0
788803
while true
789-
info = read_char_with_bytesize
804+
info = read_char_with_bytesize_slow
790805
unless info
791806
return buffer.empty? ? nil : buffer.to_s
792807
end
@@ -795,7 +810,7 @@ module IO
795810

796811
# Consider the case of \r\n when the delimiter is \n and chomp = true
797812
if chomp_rn && char == '\r'
798-
info2 = read_char_with_bytesize
813+
info2 = read_char_with_bytesize_slow
799814
unless info2
800815
buffer << char
801816
break

src/io/argf.cr

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,21 @@ class IO::ARGF
3636
first_initialize unless @initialized
3737

3838
if current_io = @current_io
39-
current_io.peek
40-
elsif !@read_from_stdin && !@argv.empty?
39+
peek = current_io.peek
40+
if peek && peek.empty? # EOF
41+
peek_next
42+
else
43+
peek
44+
end
45+
else
46+
peek_next
47+
end
48+
end
49+
50+
private def peek_next
51+
if !@read_from_stdin && !@argv.empty?
4152
read_next_argv
42-
peek
53+
self.peek
4354
else
4455
nil
4556
end

src/io/buffered.cr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ module IO::Buffered
8282
if @in_buffer_rem.empty?
8383
fill_buffer
8484
if @in_buffer_rem.empty?
85-
return nil
85+
return Bytes.empty # EOF
8686
end
8787
end
8888

src/io/memory.cr

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,8 +192,7 @@ class IO::Memory
192192
def peek
193193
check_open
194194

195-
peek = Slice.new(@buffer + @pos, @bytesize - @pos)
196-
peek.empty? ? nil : peek
195+
Slice.new(@buffer + @pos, @bytesize - @pos)
197196
end
198197

199198
# :nodoc:

src/io/sized.cr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ module IO
5959
peek = peek[0, @read_remaining]
6060
end
6161

62-
peek.empty? ? nil : peek
62+
peek
6363
end
6464

6565
def skip(bytes_count) : Nil

0 commit comments

Comments
 (0)