-
Notifications
You must be signed in to change notification settings - Fork 8
/
IOStrings.jl
105 lines (82 loc) · 2.79 KB
/
IOStrings.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
module IOStrings
using Base: @propagate_inbounds
"""
`IOString` is an abstract string that accumulates bytes from an IO stream.
`IOString(::IO)` creates a new `IOString` connected to an `IO` stream.
`pump(::IOString)` reads bytes from the `IO` stream into the string.
`incomplete(::IOString)` is true until the `IO` stream reaches `eof()`.
`pump(::Function, ::IOString)` runs a `Function` until it returns or throws
a recoverable error. When a recoverable error is thrown, more data is read
from the `IO` stream and the `Function` is restarted (the function is assumed
to be idempotent or free of side effects).
`recoverable(e) -> Bool` defines which errors are recoverable.
It the following example, `parse_item` is called repeatedly to parse items
from the string `s` at index `i`. When the parser gets to the end of the string
and throws an error, the `IOString` is pumped to retrieve more data and the
parsing continues.
```
IOString.recoverable(::MyUnexpectedEndOfInput) = true
s = IOString(socket)
i = 1
while !eof(socket)
i, v = pump(()->MyParser.parse_item(s, i), s)
println(v)
end
```
"""
struct IOString{T <: IO} <: AbstractString
io::T
buf::IOBuffer
end
struct IncompleteError <: Exception
end
const ASCII_ETB = 0x17
function IOString(io::T) where T <: IO
ios = IOString{T}(io, IOBuffer())
Base.ensureroom(ios.buf, 1)
ios.buf.data[1] = ASCII_ETB
@assert incomplete(ios)
return ios
end
Base.convert(::Type{Base.String}, s::IOString) =
unsafe_string(pointer(s), ncodeunits(s))
Base.String(s::IOString) = convert(Base.String, s)
Base.IteratorSize(::Type{IOString}) = Base.SizeUnknown()
Base.ncodeunits(s::IOString) = s.buf.size
Base.codeunit(s::IOString) = UInt8
@propagate_inbounds Base.codeunit(s::IOString, i::Integer) = s.buf.data[i]
Base.checkbounds(s::IOString, I::Union{Integer,AbstractArray}) =
checkbounds(Bool, s, I) ? nothing :
incomplete(s) ? throw(IncompleteError()) :
throw(BoundsError(s, I))
Base.pointer(s::IOString) = pointer(s.buf.data)
Base.pointer(s::IOString, i) = pointer(s.buf.data, i)
incomplete(s::IOString) = codeunit(s, ncodeunits(s) + 1) == ASCII_ETB
recoverable(e) = false
recoverable(::IncompleteError) = true
@inline function pump(f::Function, s::IOString)
while true
try
return f()
catch e
if recoverable(e)
pump(s)
else
rethrow(e)
end
end
end
end
function pump(s::IOString)
if eof(s.io)
Base.ensureroom(s.buf, 1)
s.buf.data[s.buf.size + 1] = 0x00
@assert !incomplete(s)
else
write(s.buf, readavailable(s.io))
Base.ensureroom(s.buf, 1)
s.buf.data[s.buf.size + 1] = ASCII_ETB
@assert incomplete(s)
end
end
end # module IOString