@@ -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
0 commit comments