Skip to content

Commit 3a86b7d

Browse files
non-Jedimbauman
andauthored
Add readeach function for iterating streams (#36150)
Co-authored-by: Matt Bauman <mbauman@juliacomputing.com>
1 parent 34b2ae5 commit 3a86b7d

File tree

8 files changed

+52
-18
lines changed

8 files changed

+52
-18
lines changed

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ New library functions
5151

5252
* New function `Base.kron!` and corresponding overloads for various matrix types for performing Kronecker product in-place. ([#31069]).
5353
* New function `Base.Threads.foreach(f, channel::Channel)` for multithreaded `Channel` consumption. ([#34543]).
54+
* New function `Base.readeach(io, T)` for iteratively performing `read(io, T)`. ([#36150])
5455
* `Iterators.map` is added. It provides another syntax `Iterators.map(f, iterators...)`
5556
for writing `(f(args...) for args in zip(iterators...))`, i.e. a lazy `map` ([#34352]).
5657
* New function `sincospi` for simultaneously computing `sinpi(x)` and `cospi(x)` more

base/exports.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -783,6 +783,7 @@ export
783783
close,
784784
countlines,
785785
eachline,
786+
readeach,
786787
eof,
787788
fd,
788789
fdio,

base/io.jl

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -773,8 +773,7 @@ function readuntil(s::IO, delim::AbstractChar; keep::Bool=false)
773773
return readuntil_string(s, delim % UInt8, keep)
774774
end
775775
out = IOBuffer()
776-
while !eof(s)
777-
c = read(s, Char)
776+
for c in readeach(s, Char)
778777
if c == delim
779778
keep && write(out, c)
780779
break
@@ -786,8 +785,7 @@ end
786785

787786
function readuntil(s::IO, delim::T; keep::Bool=false) where T
788787
out = (T === UInt8 ? StringVector(0) : Vector{T}())
789-
while !eof(s)
790-
c = read(s, T)
788+
for c in readeach(s, T)
791789
if c == delim
792790
keep && push!(out, c)
793791
break
@@ -823,8 +821,7 @@ function readuntil_vector!(io::IO, target::AbstractVector{T}, keep::Bool, out) w
823821
max_pos = 1 # array-offset in cache
824822
local cache # will be lazy initialized when needed
825823
output! = (isa(out, IO) ? write : push!)
826-
while !eof(io)
827-
c = read(io, T)
824+
for c in readeach(io, T)
828825
# Backtrack until the next target character matches what was found
829826
while true
830827
c1 = target[pos + first]
@@ -1022,6 +1019,40 @@ eltype(::Type{<:EachLine}) = String
10221019

10231020
IteratorSize(::Type{<:EachLine}) = SizeUnknown()
10241021

1022+
struct ReadEachIterator{T, IOT <: IO}
1023+
stream::IOT
1024+
end
1025+
1026+
"""
1027+
readeach(io::IO, T)
1028+
1029+
Return an iterable object yielding [`read(io, T)`](@ref).
1030+
1031+
See also: [`skipchars`](@ref), [`eachline`](@ref), [`readuntil`](@ref)
1032+
1033+
!!! compat "Julia 1.6"
1034+
`readeach` requires Julia 1.6 or later.
1035+
1036+
# Examples
1037+
```jldoctest
1038+
julia> io = IOBuffer("JuliaLang is a GitHub organization.\\n It has many members.\\n");
1039+
1040+
julia> for c in readeach(io, Char)
1041+
c == '\\n' && break
1042+
print(c)
1043+
end
1044+
JuliaLang is a GitHub organization.
1045+
```
1046+
"""
1047+
readeach(stream::IOT, T::Type) where IOT<:IO = ReadEachIterator{T,IOT}(stream)
1048+
1049+
iterate(itr::ReadEachIterator{T}, state=nothing) where T =
1050+
eof(itr.stream) ? nothing : (read(itr.stream, T), nothing)
1051+
1052+
eltype(::Type{ReadEachIterator{T}}) where T = T
1053+
1054+
IteratorSize(::Type{<:ReadEachIterator}) = SizeUnknown()
1055+
10251056
# IOStream Marking
10261057
# Note that these functions expect that io.mark exists for
10271058
# the concrete IO type. This may not be true for IO types
@@ -1106,8 +1137,7 @@ julia> String(readavailable(buf))
11061137
```
11071138
"""
11081139
function skipchars(predicate, io::IO; linecomment=nothing)
1109-
while !eof(io)
1110-
c = read(io, Char)
1140+
for c in readeach(io, Char)
11111141
if c === linecomment
11121142
readline(io)
11131143
elseif !predicate(c)

doc/src/base/io-network.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Base.read!
1919
Base.readbytes!
2020
Base.unsafe_read
2121
Base.unsafe_write
22+
Base.readeach
2223
Base.peek
2324
Base.position
2425
Base.seek

stdlib/Markdown/src/Common/block.jl

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@ function paragraph(stream::IO, md::MD)
1616
push!(md, p)
1717
skipwhitespace(stream)
1818
prev_char = '\n'
19-
while !eof(stream)
20-
char = read(stream, Char)
19+
for char in readeach(stream, Char)
2120
if char == '\n' || char == '\r'
2221
char == '\r' && !eof(stream) && peek(stream, Char) == '\n' && read(stream, Char)
2322
if prev_char == '\\'
@@ -339,8 +338,7 @@ end
339338
function horizontalrule(stream::IO, block::MD)
340339
withstream(stream) do
341340
n, rule = 0, ' '
342-
while !eof(stream)
343-
char = read(stream, Char)
341+
for char in readeach(stream, Char)
344342
char == '\n' && break
345343
isspace(char) && continue
346344
if n==0 || char==rule

stdlib/Markdown/src/GitHub/GitHub.jl

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,7 @@ function github_paragraph(stream::IO, md::MD)
4141
buffer = IOBuffer()
4242
p = Paragraph()
4343
push!(md, p)
44-
while !eof(stream)
45-
char = read(stream, Char)
44+
for char in readeach(stream, Char)
4645
if char == '\n'
4746
eof(stream) && break
4847
if blankline(stream) || parse(stream, md, breaking = true)

stdlib/Markdown/src/parse/util.jl

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@ Skip any leading blank lines. Returns the number skipped.
2626
function skipblank(io::IO)
2727
start = position(io)
2828
i = 0
29-
while !eof(io)
30-
c = read(io, Char)
29+
for c in readeach(io, Char)
3130
c == '\n' && (start = position(io); i+=1; continue)
3231
c == '\r' && (start = position(io); i+=1; continue)
3332
c in whitespace || break
@@ -183,8 +182,7 @@ function parse_inline_wrapper(stream::IO, delimiter::AbstractString; rep = false
183182
!eof(stream) && peek(stream, Char) in whitespace && return nothing
184183

185184
buffer = IOBuffer()
186-
while !eof(stream)
187-
char = read(stream, Char)
185+
for char in readeach(stream, Char)
188186
write(buffer, char)
189187
if !(char in whitespace || char == '\n' || char in delimiter) && startswith(stream, delimiter^n)
190188
trailing = 0

test/read.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,12 @@ for (name, f) in l
300300

301301
cleanup()
302302

303+
verbose && println("$name readeach...")
304+
@test collect(readeach(io(), Char)) == Vector{Char}(text)
305+
@test collect(readeach(io(), UInt8)) == Vector{UInt8}(text)
306+
307+
cleanup()
308+
303309
verbose && println("$name countlines...")
304310
@test countlines(io()) == countlines(IOBuffer(text))
305311

0 commit comments

Comments
 (0)