diff --git a/NEWS.md b/NEWS.md index d24ad61e15058..2ff503e4978ea 100644 --- a/NEWS.md +++ b/NEWS.md @@ -51,6 +51,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]) * `Iterators.map` is added. It provides another syntax `Iterators.map(f, iterators...)` for writing `(f(args...) for args in zip(iterators...))`, i.e. a lazy `map` ([#34352]). * New function `sincospi` for simultaneously computing `sinpi(x)` and `cospi(x)` more diff --git a/base/exports.jl b/base/exports.jl index d695714665aab..fb73e9b01dc88 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -783,6 +783,7 @@ export close, countlines, eachline, + readeach, eof, fd, fdio, diff --git a/base/io.jl b/base/io.jl index da46f90c5ae26..de06cfa24393d 100644 --- a/base/io.jl +++ b/base/io.jl @@ -773,8 +773,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 @@ -786,8 +785,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 @@ -823,8 +821,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] @@ -1022,6 +1019,40 @@ 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> io = IOBuffer("JuliaLang is a GitHub organization.\\n It has many members.\\n"); + +julia> for c in readeach(io, Char) + c == '\\n' && break + print(c) + end +JuliaLang is a GitHub organization. +``` +""" +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 @@ -1106,8 +1137,7 @@ 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) elseif !predicate(c) diff --git a/doc/src/base/io-network.md b/doc/src/base/io-network.md index 101cdc890f9a9..b798a708f22b2 100644 --- a/doc/src/base/io-network.md +++ b/doc/src/base/io-network.md @@ -19,6 +19,7 @@ Base.read! Base.readbytes! Base.unsafe_read Base.unsafe_write +Base.readeach Base.peek Base.position Base.seek diff --git a/stdlib/Markdown/src/Common/block.jl b/stdlib/Markdown/src/Common/block.jl index ba290ba1e1eb9..64f8d58efcb4f 100644 --- a/stdlib/Markdown/src/Common/block.jl +++ b/stdlib/Markdown/src/Common/block.jl @@ -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 == '\\' @@ -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 diff --git a/stdlib/Markdown/src/GitHub/GitHub.jl b/stdlib/Markdown/src/GitHub/GitHub.jl index 8718055eae1fa..493e01b085258 100644 --- a/stdlib/Markdown/src/GitHub/GitHub.jl +++ b/stdlib/Markdown/src/GitHub/GitHub.jl @@ -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) diff --git a/stdlib/Markdown/src/parse/util.jl b/stdlib/Markdown/src/parse/util.jl index 7b1e9c1c003ff..7be845c96a9fc 100644 --- a/stdlib/Markdown/src/parse/util.jl +++ b/stdlib/Markdown/src/parse/util.jl @@ -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 @@ -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 diff --git a/test/read.jl b/test/read.jl index 6961dc29d2af6..b31216811b75c 100644 --- a/test/read.jl +++ b/test/read.jl @@ -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))