Skip to content

Commit 0df996a

Browse files
committed
redesign APIs
1 parent ea231e8 commit 0df996a

File tree

11 files changed

+569
-271
lines changed

11 files changed

+569
-271
lines changed

src/TranscodingStreams.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ module TranscodingStreams
22

33
export TranscodingStream
44

5+
include("memory.jl")
6+
include("buffer.jl")
57
include("codec.jl")
68
include("state.jl")
79
include("stream.jl")
810
include("io.jl")
911
include("identity.jl")
12+
include("testtools.jl")
1013

1114
end # module

src/buffer.jl

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
# Buffer
2+
# ======
3+
4+
# Data Layout
5+
# -----------
6+
#
7+
# Buffered data are stored in `data` and two position fields are used to keep
8+
# track of buffered data and margin.
9+
#
10+
# buffered data margin
11+
# |<----------->||<----------->|
12+
# |....xxxxxxxxxXXXXXXXXXXXXXXX..............|
13+
# ^ ^ ^ ^ ^
14+
# 1 markpos bufferpos marginpos endof(data)
15+
16+
mutable struct Buffer
17+
data::Vector{UInt8}
18+
markpos::Int
19+
bufferpos::Int
20+
marginpos::Int
21+
22+
function Buffer(size::Integer)
23+
@assert size > 0
24+
return new(Vector{UInt8}(size), 0, 1, 1)
25+
end
26+
end
27+
28+
function Base.length(buf::Buffer)
29+
return length(buf.data)
30+
end
31+
32+
function bufferptr(buf)
33+
return pointer(buf.data, buf.bufferpos)
34+
end
35+
36+
function buffersize(buf)
37+
return buf.marginpos - buf.bufferpos
38+
end
39+
40+
function buffermem(buf)
41+
return Memory(bufferptr(buf), buffersize(buf))
42+
end
43+
44+
function readbyte!(buf)
45+
b = buf.data[buf.bufferpos]
46+
buf.bufferpos += 1
47+
return b
48+
end
49+
50+
function writebyte!(buf, b::UInt8)
51+
buf.data[buf.marginpos] = b
52+
buf.marginpos += 1
53+
return 1
54+
end
55+
56+
function marginptr(buf)
57+
return pointer(buf.data, buf.marginpos)
58+
end
59+
60+
function marginsize(buf)
61+
return endof(buf.data) - buf.marginpos + 1
62+
end
63+
64+
function marginmem(buf)
65+
return Memory(marginptr(buf), marginsize(buf))
66+
end
67+
68+
function mark!(buf::Buffer)
69+
return buf.markpos = buf.bufferpos
70+
end
71+
72+
function unmark!(buf::Buffer)
73+
if buf.markpos == 0
74+
return false
75+
else
76+
buf.markpos = 0
77+
return true
78+
end
79+
end
80+
81+
function reset!(buf::Buffer)
82+
if buf.markpos == 0
83+
throw(ArgumentError("not marked"))
84+
end
85+
buf.bufferpos = buf.markpos
86+
buf.markpos = 0
87+
return buf.bufferpos
88+
end
89+
90+
# Make margin with ≥`minsize`.
91+
function makemargin!(buf, minsize::Int)::Int
92+
if buffersize(buf) == 0
93+
if buf.markpos == 0
94+
buf.bufferpos = buf.marginpos = 1
95+
else
96+
buf.bufferpos = buf.marginpos = buf.markpos
97+
end
98+
end
99+
if marginsize(buf) < minsize
100+
# shift data to left
101+
if buf.markpos == 0
102+
datapos = buf.bufferpos
103+
datasize = buffersize(buf)
104+
else
105+
datapos = buf.markpos
106+
datasize = buffersize(buf) + buf.bufferpos - buf.markpos
107+
end
108+
copy!(buf.data, 1, buf.data, datapos, datasize)
109+
buf.bufferpos -= datapos - 1
110+
buf.marginpos = buf.bufferpos + datasize
111+
end
112+
if marginsize(buf) < minsize
113+
# expand data buffer
114+
resize!(buf.data, buf.marginpos + minsize - 1)
115+
end
116+
@assert marginsize(buf) minsize
117+
return marginsize(buf)
118+
end
119+
120+
function emptybuffer!(buf::Buffer)
121+
buf.marginpos = buf.bufferpos
122+
return buf
123+
end
124+
125+
function skipbuffer!(buf::Buffer, n::Integer)
126+
if n > buffersize(buf)
127+
throw(ArgumentError("too large skip size"))
128+
end
129+
buf.bufferpos += n
130+
return buf
131+
end
132+
133+
# Discard buffered data and initialize positions.
134+
function initbuffer!(buf::Buffer)
135+
buf.markpos = 0
136+
buf.bufferpos = buf.marginpos = 1
137+
return buf
138+
end
139+
140+
# Read as much data as possbile from `input` to the margin of `output`.
141+
# This function will not block if `input` has buffered data.
142+
function readdata!(input::IO, output::Buffer)
143+
nread::Int = 0
144+
navail = nb_available(input)
145+
if navail == 0 && marginsize(output) > 0 && !eof(input)
146+
nread += writebyte!(output, read(input, UInt8))
147+
navail = nb_available(input)
148+
end
149+
n = min(navail, marginsize(output))
150+
Base.unsafe_read(input, marginptr(output), n)
151+
output.marginpos += n
152+
nread += n
153+
return nread
154+
end
155+
156+
function readdata!(buf::Buffer, dst::Vector{UInt8}, dpos::Integer, sz::Integer)
157+
copy!(dst, dpos, buf.data, buf.bufferpos, sz)
158+
buf.bufferpos += sz
159+
return dst
160+
end
161+
162+
# Write as much data as possible to `output` from the buffer of `input`.
163+
function writebuffer!(output::IO, input::Buffer)
164+
while buffersize(input) > 0
165+
input.bufferpos += Base.unsafe_write(output, bufferptr(input), buffersize(input))
166+
end
167+
end
168+
169+
function findbyte(buf, byte::UInt8)
170+
ptr = ccall(:memchr, Ptr{Void}, (Ptr{Void}, Cint, Csize_t), pointer(buf.data, buf.bufferpos), byte, buffersize(buf))
171+
if ptr == C_NULL
172+
return 0
173+
else
174+
return Int(ptr - pointer(buf.data, buf.bufferpos)) + buf.bufferpos
175+
end
176+
end

src/codec.jl

Lines changed: 21 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,124 +1,50 @@
11
# Codec Interfaces
22
# ================
33

4-
abstract type Codec end
5-
6-
abstract type Mode end
7-
struct Read <: Mode end
8-
struct Write <: Mode end
9-
10-
primitive type ProcCode 8 end
11-
const PROC_INIT = reinterpret(ProcCode, 0x00)
12-
const PROC_OK = reinterpret(ProcCode, 0x01)
13-
const PROC_FINISH = reinterpret(ProcCode, 0x02)
14-
15-
16-
# Start
17-
# -----
18-
194
"""
20-
start(::Type{Read}, codec::Codec, source::IO)::Void
21-
22-
Start transcoding using `codec` with read mode.
5+
An abstract codec type.
236
24-
This method will be called only once before calling `process` first time.
25-
26-
Implementing this method is optional; there is a default method that does nothing.
7+
Any codec supporting transcoding interfaces must be a subtype of this type.
278
"""
28-
function start(::Type{Read}, ::Codec, ::IO)
29-
return nothing
30-
end
31-
32-
"""
33-
start(::Type{Write}, codec::Codec, sink::IO)::Void
34-
35-
Start transcoding using `codec` with write mode.
36-
37-
This method will be called only once before calling `process` first time.
38-
39-
Implementing this method is optional; there is a default method that does nothing.
40-
"""
41-
function start(::Type{Write}, ::Codec, ::IO)
42-
return nothing
43-
end
9+
abstract type Codec end
4410

4511

46-
# Process
12+
# Methods
4713
# -------
4814

4915
"""
50-
process(::Type{Read}, codec::Codec, source::IO, output::Ptr{UInt8}, nbytes::Int)::Tuple{Int,ProcCode}
51-
52-
Transcode data using `codec` with read mode.
53-
54-
<> >-----(process)-----> [........]
55-
source codec output
56-
57-
This method reads some data from `source` and write the transcoded data to
58-
`output` at most `nbytes`, and then returns the number of written bytes and an
59-
appropriate return code. It will be called repeatedly to incrementally
60-
transcode input data from `source`. It can assume `output` points to a valid
61-
memory position and `nbytes` are positive. The intermediate result of
62-
transcoding is indicated by the return value of it. If processing works
63-
properly and there are still data to write, it must return the number of written
64-
bytes and `PROC_OK`. If processing finishes and there are no remaining data to
65-
write, it must return the number of written bytes and `PROC_FINISH`. Therefore,
66-
after finishing reading data from `source` and all (possibly buffered) data are
67-
written to `output`, it is expected to return `(0, PROC_FINISH)` forever.
16+
initialize(codec::Codec)::Void
6817
69-
This method may throw an exception when transcoding fails. For example, when
70-
`codec` is a decompression algorithm, it may throw an exception if it reads
71-
malfomed data from `source`. However, this method must not throw `EOFError`
72-
when reading data from `source`. Also note that it is responsible for this
73-
method to release resources allocated by `codec` when an exception happens.
18+
Initialize `codec`.
7419
"""
75-
function process(::Type{Read}, codec::Codec, source::IO, input::Ptr{UInt8}, nbytes::Int)
76-
error("codec $(typeof(codec)) does not implement read mode")
20+
function initialize(codec::Codec)
21+
return nothing
7722
end
7823

7924
"""
80-
process(::Type{Write}, codec::Codec, sink::IO, input::Ptr{UInt8}, nbytes::Int)::Tuple{Int,ProcCode}
81-
82-
Transcode data using `codec` with write mode.
25+
finalize(codec::Codec)::Void
8326
84-
<> <-----(transcode)-----< [.......]
85-
sink codec input
86-
87-
This method reads some data from `input` and write the transcoded bytes to
88-
`sink` at most `nbytes`, and then returns the number of written bytes and an
89-
appropriate return code. It can assume `input` points to a valid memory position
90-
and `nbytes` is positive.
27+
Finalize `codec`.
9128
"""
92-
function process(::Type{Write}, codec::Codec, sink::IO, input::Ptr{UInt8}, nbytes::Int)
93-
error("codec $(typeof(codec)) does not implement write mode")
29+
function finalize(codec::Codec)::Void
30+
return nothing
9431
end
9532

96-
97-
# Finish
98-
# ------
99-
10033
"""
101-
finish(::Type{Read}, codec::Codec, source::IO)::Void
102-
103-
Finish transcoding using `codec` with read mode.
104-
105-
This method will be called only once after calling `process` last time.
34+
startproc(codec::Codec, state::Symbol)::Symbol
10635
107-
Implementing this method is optional; there is a default method that does nothing.
36+
Start data processing with `codec` of `state`.
10837
"""
109-
function finish(::Type{Read}, ::Codec, ::IO)
110-
return nothing
38+
function startproc(codec::Codec, state::Symbol)::Symbol
39+
return :ok
11140
end
11241

11342
"""
114-
finish(::Type{Write}, codec::Codec, sink::IO)::Void
115-
116-
Finish transcoding using `codec` with write mode.
43+
process(codec::Codec, input::Memory, output::Memory)::Tuple{Int,Int,Symbol}
11744
118-
This method will be called only once after calling `process` last time.
119-
120-
Implementing this method is optional; there is a default method that does nothing.
45+
Do data processing with `codec`.
12146
"""
122-
function finish(::Type{Write}, ::Codec, ::IO)
123-
return nothing
47+
function process(codec::Codec, input::Memory, output::Memory)::Tuple{Int,Int,Symbol}
48+
# no default method
49+
throw(MethodError(process, (codec, input, output)))
12450
end

src/identity.jl

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,8 @@
33

44
struct Identity <: Codec end
55

6-
function process(::Type{Read}, ::Identity, stream::IO, data::Ptr{UInt8}, nbytes::Int)::Tuple{Int,ProcCode}
7-
n = unsafe_read(stream, data, nbytes)
8-
if eof(stream)
9-
return n, PROC_FINISH
10-
else
11-
return n, PROC_OK
12-
end
13-
end
14-
15-
function process(::Type{Write}, ::Identity, stream::IO, data::Ptr{UInt8}, nbytes::Int)::Tuple{Int,ProcCode}
16-
n = unsafe_write(stream, data, nbytes)
17-
return Int(n), PROC_OK
6+
function process(::Identity, input::Memory, output::Memory)
7+
n = Int(min(input.size, output.size))
8+
unsafe_copy!(output.ptr, input.ptr, n)
9+
return n, n, ifelse(input.size == 0, :end, :ok)
1810
end

src/io.jl

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,15 @@ function unsafe_read(input::IO, output::Ptr{UInt8}, nbytes::Int)::Int
2727
nread += n
2828
return nread
2929
end
30+
31+
function unsafe_read(input::IO, mem::Memory)
32+
return unsafe_read(input, mem.ptr, Int(mem.size))
33+
end
34+
35+
function unsafe_write(output::IO, ptr::Ptr{UInt8}, nbytes::Int)::Int
36+
return Base.unsafe_write(output, ptr, nbytes)
37+
end
38+
39+
function unsafe_write(output::IO, mem::Memory)
40+
return Base.unsafe_write(output, mem.ptr, mem.size)
41+
end

src/memory.jl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Memory
2+
# ======
3+
4+
immutable Memory
5+
ptr::Ptr{UInt8}
6+
size::UInt
7+
end
8+
9+
function Base.length(mem::Memory)
10+
return mem.size
11+
end

0 commit comments

Comments
 (0)