Skip to content

Commit

Permalink
IO#peek now returns an empty slice on EOF. Fixes #4240
Browse files Browse the repository at this point in the history
  • Loading branch information
asterite committed Apr 5, 2017
1 parent c01a6c7 commit eae4193
Show file tree
Hide file tree
Showing 9 changed files with 53 additions and 28 deletions.
4 changes: 2 additions & 2 deletions spec/std/io/buffered_spec.cr
Expand Up @@ -315,8 +315,8 @@ describe "IO::Buffered" do
# Peek doesn't advance
io.gets_to_end.should eq("foo")

# Returns nil if no more data
io.peek.should be_nil
# Returns EOF if no more data
io.peek.should eq(Bytes.empty)
end

it "skips" do
Expand Down
2 changes: 1 addition & 1 deletion spec/std/io/memory_spec.cr
Expand Up @@ -342,7 +342,7 @@ describe IO::Memory do
io.peek.should eq("lo world".to_slice)

io.skip_to_end
io.peek.should be_nil
io.peek.should eq(Bytes.empty)
end

it "skips" do
Expand Down
2 changes: 1 addition & 1 deletion spec/std/io/sized_spec.cr
Expand Up @@ -114,7 +114,7 @@ describe "IO::Sized" do
sized = IO::Sized.new(io, read_size: 6)
sized.peek.should eq("123456".to_slice)
sized.gets_to_end.should eq("123456")
sized.peek.should be_nil
sized.peek.should eq(Bytes.empty)
end

it "skips" do
Expand Down
2 changes: 1 addition & 1 deletion src/http/content.cr
Expand Up @@ -102,7 +102,7 @@ module HTTP
peek = peek[0, @chunk_remaining]
end

return peek.empty? ? nil : peek
return peek
elsif @read_chunk_start
read_chunk_start
next
Expand Down
47 changes: 31 additions & 16 deletions src/io.cr
Expand Up @@ -392,35 +392,36 @@ module IO
private def read_char_with_bytesize
# For UTF-8 encoding, try to see if we can peek 4 bytes.
# If so, this will be faster than reading byte per byte.
if !decoder && (peek = self.peek) && peek.size == 4
read_char_with_bytesize_peek(peek)
if !decoder && (peek = self.peek)
if peek.empty?
return nil
else
return read_char_with_bytesize_peek(peek)
end
else
read_char_with_bytesize_slow
end
end

private def read_char_with_bytesize_peek(peek)
first = peek[0].to_u32
skip(1)
if first < 0x80
skip(1)
return first.unsafe_chr, 1
end

second = (peek[1] & 0x3f).to_u32
second = peek_or_read_masked(peek, 1)
if first < 0xe0
skip(2)
return ((first & 0x1f) << 6 | second).unsafe_chr, 2
end

third = (peek[2] & 0x3f).to_u32
third = peek_or_read_masked(peek, 2)
if first < 0xf0
skip(3)
return ((first & 0x0f) << 12 | (second << 6) | third).unsafe_chr, 3
end

fourth = (peek[3] & 0x3f).to_u32
fourth = peek_or_read_masked(peek, 3)
if first < 0xf8
skip(4)
return ((first & 0x07) << 18 | (second << 12) | (third << 6) | fourth).unsafe_chr, 4
end

Expand Down Expand Up @@ -451,6 +452,15 @@ module IO
(byte & 0x3f).to_u32
end

private def peek_or_read_masked(peek, index)
if byte = peek[index]?
skip(1)
(byte & 0x3f).to_u32
else
read_utf8_masked_byte
end
end

# Reads a single decoded UTF-8 byte from this `IO`.
# Returns `nil` if there is no more data to read.
#
Expand Down Expand Up @@ -527,9 +537,10 @@ module IO

# Peeks into this IO, if possible.
#
# If this IO can peek into some data, it returns a slice
# with that data. Returns `nil` if this IO isn't peekable,
# or if there's no data to peek.
# It returns:
# - `nil` if this IO isn't peekable
# - an empty slice if it is, but EOF was reached
# - a non-empty slice if some data can be peeked
#
# The returned bytes are only valid data until a next call
# to any method that reads from this IO is invoked.
Expand Down Expand Up @@ -698,7 +709,11 @@ module IO
# If there's no encoding, the delimiter is ASCII and we can peek,
# use a faster algorithm
if ascii && !decoder && (peek = self.peek)
gets_peek(delimiter, limit, chomp, peek)
if peek.empty?
nil
else
gets_peek(delimiter, limit, chomp, peek)
end
else
gets_slow(delimiter, limit, chomp)
end
Expand Down Expand Up @@ -756,7 +771,7 @@ module IO
peek = self.peek
end

unless peek
if !peek || peek.empty?
if buffer.bytesize == 0
return nil
else
Expand Down Expand Up @@ -786,7 +801,7 @@ module IO
buffer = String::Builder.new
total = 0
while true
info = read_char_with_bytesize
info = read_char_with_bytesize_slow
unless info
return buffer.empty? ? nil : buffer.to_s
end
Expand All @@ -795,7 +810,7 @@ module IO

# Consider the case of \r\n when the delimiter is \n and chomp = true
if chomp_rn && char == '\r'
info2 = read_char_with_bytesize
info2 = read_char_with_bytesize_slow
unless info2
buffer << char
break
Expand Down
17 changes: 14 additions & 3 deletions src/io/argf.cr
Expand Up @@ -36,10 +36,21 @@ class IO::ARGF
first_initialize unless @initialized

if current_io = @current_io
current_io.peek
elsif !@read_from_stdin && !@argv.empty?
peek = current_io.peek
if peek && peek.empty? # EOF
peek_next
else
peek
end
else
peek_next
end
end

private def peek_next
if !@read_from_stdin && !@argv.empty?
read_next_argv
peek
self.peek
else
nil
end
Expand Down
2 changes: 1 addition & 1 deletion src/io/buffered.cr
Expand Up @@ -82,7 +82,7 @@ module IO::Buffered
if @in_buffer_rem.empty?
fill_buffer
if @in_buffer_rem.empty?
return nil
return Bytes.empty # EOF
end
end

Expand Down
3 changes: 1 addition & 2 deletions src/io/memory.cr
Expand Up @@ -192,8 +192,7 @@ class IO::Memory
def peek
check_open

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

# :nodoc:
Expand Down
2 changes: 1 addition & 1 deletion src/io/sized.cr
Expand Up @@ -59,7 +59,7 @@ module IO
peek = peek[0, @read_remaining]
end

peek.empty? ? nil : peek
peek
end

def skip(bytes_count) : Nil
Expand Down

0 comments on commit eae4193

Please sign in to comment.