Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add readeach function for iterating streams #36150

Merged
merged 9 commits into from
Jul 23, 2020
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ New library functions

* New function `Base.kron!` and corresponding overloads for various matrix types for performing Kronecker product in-place. ([#31069]).
* New function `Base.Threads.foreach(f, channel::Channel)` for multithreaded `Channel` consumption. ([#34543]).
* New function `Base.readeach(io, T)` for iteratively performing `read(io, T)`. ([#36150])

New library features
--------------------
Expand Down
1 change: 1 addition & 0 deletions base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,7 @@ export
close,
countlines,
eachline,
readeach,
eof,
fd,
fdio,
Expand Down
55 changes: 46 additions & 9 deletions base/io.jl
Original file line number Diff line number Diff line change
Expand Up @@ -754,8 +754,7 @@ function readuntil(s::IO, delim::AbstractChar; keep::Bool=false)
return readuntil_string(s, delim % UInt8, keep)
end
out = IOBuffer()
while !eof(s)
c = read(s, Char)
for c in readeach(s, Char)
if c == delim
keep && write(out, c)
break
Expand All @@ -767,8 +766,7 @@ end

function readuntil(s::IO, delim::T; keep::Bool=false) where T
out = (T === UInt8 ? StringVector(0) : Vector{T}())
while !eof(s)
c = read(s, T)
for c in readeach(s, T)
if c == delim
keep && push!(out, c)
break
Expand Down Expand Up @@ -804,8 +802,7 @@ function readuntil_vector!(io::IO, target::AbstractVector{T}, keep::Bool, out) w
max_pos = 1 # array-offset in cache
local cache # will be lazy initialized when needed
output! = (isa(out, IO) ? write : push!)
while !eof(io)
c = read(io, T)
for c in readeach(io, T)
# Backtrack until the next target character matches what was found
while true
c1 = target[pos + first]
Expand Down Expand Up @@ -1003,6 +1000,46 @@ eltype(::Type{<:EachLine}) = String

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

struct ReadEachIterator{T, IOT <: IO}
stream::IOT
end

"""
readeach(io::IO, T)

Return an iterable object yielding [`read(io, T)`](@ref).

See also: [`skipchars`](@ref), [`eachline`](@ref), [`readuntil`](@ref)

!!! compat "Julia 1.6"
`readeach` requires Julia 1.6 or later.

# Examples
```jldoctest
julia> open("my_file.txt", "w") do io
write(io, "JuliaLang is a GitHub organization.\\n It has many members.\\n");
non-Jedi marked this conversation as resolved.
Show resolved Hide resolved
end;

julia> open("my_file.txt") do io
for c in readeach(io, Char)
c == '\\n' && break
non-Jedi marked this conversation as resolved.
Show resolved Hide resolved
print(c)
end
end
JuliaLang is a GitHub organization.

julia> rm("my_file.txt");
```
"""
readeach(stream::IOT, T::Type) where IOT<:IO = ReadEachIterator{T,IOT}(stream)

iterate(itr::ReadEachIterator{T}, state=nothing) where T =
eof(itr.stream) ? nothing : (read(itr.stream, T), nothing)

eltype(::Type{ReadEachIterator{T}}) where T = T

IteratorSize(::Type{<:ReadEachIterator}) = SizeUnknown()

# IOStream Marking
# Note that these functions expect that io.mark exists for
# the concrete IO type. This may not be true for IO types
Expand Down Expand Up @@ -1087,10 +1124,10 @@ julia> String(readavailable(buf))
```
"""
function skipchars(predicate, io::IO; linecomment=nothing)
while !eof(io)
c = read(io, Char)
for c in readeach(io, Char)
if c === linecomment
readline(io)
non-Jedi marked this conversation as resolved.
Show resolved Hide resolved
skipchars(c -> c !== '\n', io)
read(io, Char)
elseif !predicate(c)
skip(io, -ncodeunits(c))
break
Expand Down
1 change: 1 addition & 0 deletions doc/src/base/io-network.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Base.read!
Base.readbytes!
Base.unsafe_read
Base.unsafe_write
Base.readeach
Base.peek
Base.position
Base.seek
Expand Down
6 changes: 2 additions & 4 deletions stdlib/Markdown/src/Common/block.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ function paragraph(stream::IO, md::MD)
push!(md, p)
skipwhitespace(stream)
prev_char = '\n'
while !eof(stream)
char = read(stream, Char)
for char in readeach(stream, Char)
if char == '\n' || char == '\r'
char == '\r' && !eof(stream) && peek(stream, Char) == '\n' && read(stream, Char)
if prev_char == '\\'
Expand Down Expand Up @@ -339,8 +338,7 @@ end
function horizontalrule(stream::IO, block::MD)
withstream(stream) do
n, rule = 0, ' '
while !eof(stream)
char = read(stream, Char)
for char in readeach(stream, Char)
char == '\n' && break
isspace(char) && continue
if n==0 || char==rule
Expand Down
3 changes: 1 addition & 2 deletions stdlib/Markdown/src/GitHub/GitHub.jl
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,7 @@ function github_paragraph(stream::IO, md::MD)
buffer = IOBuffer()
p = Paragraph()
push!(md, p)
while !eof(stream)
char = read(stream, Char)
for char in readeach(stream, Char)
if char == '\n'
eof(stream) && break
if blankline(stream) || parse(stream, md, breaking = true)
Expand Down
6 changes: 2 additions & 4 deletions stdlib/Markdown/src/parse/util.jl
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@ Skip any leading blank lines. Returns the number skipped.
function skipblank(io::IO)
start = position(io)
i = 0
while !eof(io)
c = read(io, Char)
for c in readeach(io, Char)
c == '\n' && (start = position(io); i+=1; continue)
c == '\r' && (start = position(io); i+=1; continue)
c in whitespace || break
Expand Down Expand Up @@ -183,8 +182,7 @@ function parse_inline_wrapper(stream::IO, delimiter::AbstractString; rep = false
!eof(stream) && peek(stream, Char) in whitespace && return nothing

buffer = IOBuffer()
while !eof(stream)
char = read(stream, Char)
for char in readeach(stream, Char)
write(buffer, char)
if !(char in whitespace || char == '\n' || char in delimiter) && startswith(stream, delimiter^n)
trailing = 0
Expand Down
6 changes: 6 additions & 0 deletions test/read.jl
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,12 @@ for (name, f) in l

cleanup()

verbose && println("$name readeach...")
@test collect(readeach(io(), Char)) == Vector{Char}(text)
@test collect(readeach(io(), UInt8)) == Vector{UInt8}(text)

cleanup()

verbose && println("$name countlines...")
@test countlines(io()) == countlines(IOBuffer(text))

Expand Down