-
Notifications
You must be signed in to change notification settings - Fork 15
/
simple-bit-stream.lisp
370 lines (333 loc) · 14.4 KB
/
simple-bit-stream.lisp
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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
(defpackage :simple-bit-stream
(:use :common-lisp :trivial-gray-streams :lisp-binary/integer :lisp-binary-utils)
(:export :wrap-in-bit-stream :with-wrapped-in-bit-stream :bit-stream :read-bits
:write-bits :read-bytes-with-partial :byte-aligned-p))
(in-package :simple-bit-stream)
(defclass bit-stream (fundamental-binary-stream fundamental-input-stream fundamental-output-stream)
((element-bits :type fixnum :initform 8 :initarg :element-bits)
(real-stream :type stream :initarg :real-stream)
(last-byte :type unsigned-byte :initform 0)
(last-op :type symbol :initform nil)
(bits-left :type integer :initform 0)
(byte-order :type keyword :initarg :byte-order :initform :little-endian)))
(defmethod stream-element-type ((stream bit-stream))
'(unsigned-byte 8))
(defun trace-read-byte (stream &optional (eof-error-p t) eof-value)
(read-byte stream eof-error-p eof-value))
(defun trace-write-byte (byte stream)
(write-byte byte stream))
(defun byte-aligned-p (bit-stream)
(= (slot-value bit-stream 'bits-left) 0))
(defgeneric wrap-in-bit-stream (object &key byte-order)
(:documentation "Creates a BIT-STREAM that can read one bit at a time from the OBJECT. The BIT-STREAM
can be discarded if BYTE-ALIGNED-P returns T."))
(defmethod wrap-in-bit-stream ((object stream) &key (byte-order :little-endian))
(make-instance 'bit-stream :real-stream object :byte-order byte-order))
(defmacro with-wrapped-in-bit-stream ((var non-bitstream &key (byte-order :little-endian)
close-when-done) &body body)
`(let ((,var (wrap-in-bit-stream ,non-bitstream :byte-order ,byte-order)))
(unwind-protect
(progn
,@body)
(finish-output ,var)
,@(if close-when-done
`((if ,close-when-done
(close ,var)))))))
(declaim (inline init-read init-write reset-op))
(defun reset-op (stream op)
(setf (slot-value stream 'last-op) op)
(setf (slot-value stream 'last-byte) 0)
(setf (slot-value stream 'bits-left) 0))
(defun init-read (stream)
(unless (eq (slot-value stream 'last-op) :read)
(reset-op stream :read)))
(defun init-write (stream)
(unless (eq (slot-value stream 'last-op) :write)
(reset-op stream :write)))
(defun read-partial-byte/big-endian (bits stream)
(cond
((= (slot-value stream 'bits-left) 0)
(setf (slot-value stream 'last-byte)
(restart-case (trace-read-byte (slot-value stream 'real-stream))
(continue ()
:report "Pretend the read returned a 0 byte."
0)))
(setf (slot-value stream 'bits-left)
(slot-value stream 'element-bits))
(read-partial-byte/big-endian bits stream))
((>= (slot-value stream 'bits-left) bits)
(prog1
(pop-bits bits (slot-value stream 'bits-left)
(slot-value stream 'last-byte))
(decf (slot-value stream 'bits-left) bits)))
((< (slot-value stream 'bits-left) bits)
(let* ((bits-left (slot-value stream 'bits-left))
(remaining-bits (pop-bits (slot-value stream 'bits-left)
bits-left
(slot-value stream 'last-byte))))
(setf (slot-value stream 'bits-left) 0)
(logior
(ash remaining-bits (- bits bits-left))
(read-partial-byte/big-endian (- bits bits-left) stream))))))
(defun read-partial-byte/little-endian (bits stream)
(cond
((= (slot-value stream 'bits-left) 0)
(setf (slot-value stream 'last-byte)
(restart-case (trace-read-byte (slot-value stream 'real-stream))
(continue ()
:report "Pretend the read returned a 0 byte."
0)))
(setf (slot-value stream 'bits-left)
(slot-value stream 'element-bits))
(read-partial-byte/little-endian bits stream))
((>= (slot-value stream 'bits-left) bits)
(prog1
(pop-bits/le bits (slot-value stream 'last-byte))
(decf (slot-value stream 'bits-left) bits)))
((< (slot-value stream 'bits-left) bits)
(let* ((bits-left (slot-value stream 'bits-left))
(remaining-bits (pop-bits/le bits-left (slot-value stream 'last-byte))))
(setf (slot-value stream 'bits-left) 0)
(logior
remaining-bits
(ash (read-partial-byte/little-endian (- bits bits-left) stream)
bits-left))))
(t (error "BUG: This should never happen!"))))
(defmethod stream-finish-output ((stream bit-stream))
(unless (or (not (eq (slot-value stream 'last-op) :write))
(= (slot-value stream 'bits-left) 0))
(trace-write-byte (ecase (slot-value stream 'byte-order)
(:little-endian (slot-value stream 'last-byte))
(:big-endian (ash (slot-value stream 'last-byte)
(- 8 (slot-value stream 'bits-left)))))
(slot-value stream 'real-stream))
(finish-output (slot-value stream 'real-stream))))
(defmethod stream-force-output ((stream bit-stream))
(stream-finish-output stream))
(defmethod close ((stream bit-stream) &key abort)
(declare (ignore abort))
(stream-finish-output stream)
(close (slot-value stream 'real-stream)))
(defmethod stream-read-byte ((stream bit-stream))
(init-read stream)
(cond ((= (slot-value stream 'bits-left) 0)
(trace-read-byte (slot-value stream 'real-stream)))
((= (slot-value stream 'bits-left)
(slot-value stream 'element-bits))
(prog1
(slot-value stream 'last-byte)
(setf (slot-value stream 'last-byte) 0
(slot-value stream 'bits-left) 0)))
((< (slot-value stream 'bits-left)
(slot-value stream 'element-bits))
(ecase (slot-value stream 'byte-order)
(:little-endian
(let ((bits-left (slot-value stream 'bits-left))
(last-byte (slot-value stream 'last-byte))
(next-bits nil))
(setf (slot-value stream 'bits-left) 0)
(setf (slot-value stream 'last-byte) 0)
(setf next-bits (read-partial-byte/little-endian (- (slot-value stream 'element-bits)
bits-left)
stream))
(logior last-byte
(ash next-bits bits-left))))
(:big-endian
(logior (ash (slot-value stream 'last-byte)
(- (slot-value stream 'element-bits)
(slot-value stream 'bits-left)))
(let ((bits-to-read (- (slot-value stream 'element-bits)
(slot-value stream 'bits-left))))
(setf (slot-value stream 'last-byte) 0
(slot-value stream 'bits-left) 0)
(read-partial-byte/big-endian bits-to-read stream))))))
((> (slot-value stream 'bits-left)
(slot-value stream 'element-bits))
(ecase (slot-value stream 'byte-order)
(:little-endian
(prog1 (logand (1- (expt 2 (slot-value stream 'element-bits)))
(slot-value stream 'last-byte))
(decf (slot-value stream 'bits-left)
(slot-value stream 'element-bits))
(setf (slot-value stream 'last-byte)
(ash (slot-value stream 'last-byte)
(- (slot-value stream 'element-bits))))))
(:big-endian
(error "Not implemented!"))))))
(defmethod stream-write-byte ((stream bit-stream) integer)
(init-write stream)
(cond ((= (slot-value stream 'bits-left) 0)
(trace-write-byte integer (slot-value stream 'real-stream)))
(t (let ((total-bits-left (+ (slot-value stream 'element-bits)
(slot-value stream 'bits-left))))
(multiple-value-bind (byte-to-write new-last-byte)
(ecase (slot-value stream 'byte-order)
(:little-endian
(push-bits integer (slot-value stream 'bits-left)
(slot-value stream 'last-byte))
(values (pop-bits/le (slot-value stream 'element-bits)
(slot-value stream 'last-byte))
(slot-value stream 'last-byte)))
(:big-endian
(push-bits/le integer (slot-value stream 'element-bits)
(slot-value stream 'last-byte))
(values (pop-bits (slot-value stream 'element-bits)
total-bits-left
(slot-value stream 'last-byte))
(slot-value stream 'last-byte))))
(setf (slot-value stream 'last-byte) new-last-byte)
(trace-write-byte byte-to-write (slot-value stream 'real-stream)))))))
(defun %stream-write-sequence (stream sequence start end)
(unless (>= end start)
(return-from %stream-write-sequence sequence))
(cond ((and (equal (slot-value stream 'bits-left) 0)
(streamp (slot-value stream 'real-stream)))
(write-sequence sequence (slot-value stream 'real-stream) :start start :end end))
(t (loop for ix from start below end
do (trace-write-byte (elt sequence ix) stream))
sequence)))
(defmethod stream-write-sequence ((stream bit-stream) sequence start end &key &allow-other-keys)
(%stream-write-sequence stream sequence (or start 0) (or end (1- (length sequence)))))
(defun %stream-read-sequence (stream sequence start end)
(declare (optimize (speed 0) (debug 3)))
(unless (> end start)
(return-from %stream-read-sequence 0))
(init-read stream)
(cond ((and (equal (slot-value stream 'bits-left) 0)
(streamp (slot-value stream 'real-stream)))
(read-sequence sequence (slot-value stream 'real-stream) :start start :end end))
(t
(loop for ix from start below end
do (setf (elt sequence ix)
(handler-case
(trace-read-byte stream)
(end-of-file ()
(return bytes-read))))
count t into bytes-read
finally (return bytes-read)))))
(defmethod stream-read-sequence ((stream bit-stream) sequence start end &key &allow-other-keys)
(%stream-read-sequence stream sequence start end))
(defmacro read-bytes-with-partial/macro (stream* bits byte-order &key adjustable)
(alexandria:with-gensyms (whole-bytes remaining-bits element-bits buffer
stream bytes-read)
`(let* ((,stream ,stream*)
(,element-bits (slot-value ,stream 'element-bits)))
(multiple-value-bind (,whole-bytes ,remaining-bits)
(floor ,bits ,element-bits)
(let ((,buffer (make-array ,whole-bytes
:element-type (list 'unsigned-byte ,element-bits)
:adjustable ,adjustable
:fill-pointer ,adjustable)))
(when (> ,whole-bytes 0)
(let ((,bytes-read (read-sequence ,buffer ,stream)))
(when (< ,bytes-read ,whole-bytes)
(cerror "Ignore the error." (make-condition 'end-of-file :stream ,stream)))))
(values ,buffer ,(ecase byte-order
(:little-endian
`(read-partial-byte/little-endian ,remaining-bits ,stream))
(:big-endian
`(read-partial-byte/big-endian ,remaining-bits, stream)))
,remaining-bits))))))
(defun read-bytes-with-partial (stream bits)
"Reads BITS bits from the STREAM, where BITS is expected to be more than a byte's worth
of bits. Returns three values:
1. A buffer containing as many whole bytes as possible. This buffer
is always read first, regardless of whether the bitstream is byte-aligned.
2. The partial byte.
3. The number of bits that were read for the partial byte.
The byte order is determined from the STREAM object, which must be a SIMPLE-BIT-STREAM:BIT-STREAM."
(ecase (slot-value stream 'byte-order)
(:big-endian
(init-read stream)
(read-bytes-with-partial/macro stream bits :big-endian :adjustable t))
(:little-endian
(init-read stream)
(read-bytes-with-partial/macro stream bits :little-endian :adjustable t))))
(defun read-bits/big-endian (bits stream)
(cond ((< bits (slot-value stream 'element-bits))
(read-partial-byte/big-endian bits stream))
((= bits (slot-value stream 'element-bits))
(trace-read-byte stream))
(t
(let ((result 0)
(element-bits (slot-value stream 'element-bits)))
(multiple-value-bind (buffer partial-byte remaining-bits)
(read-bytes-with-partial/macro stream bits :big-endian)
(loop for byte across buffer
for bit-shift from bits downto remaining-bits by element-bits
do (incf result (ash byte bit-shift)))
(logior result partial-byte))))))
(defun read-bits/little-endian (bits stream)
(cond ((< bits (slot-value stream 'element-bits))
(read-partial-byte/little-endian bits stream))
((= bits (slot-value stream 'element-bits))
(trace-read-byte stream))
(t
(let ((result 0)
(element-bits (slot-value stream 'element-bits)))
(multiple-value-bind (buffer partial-byte remaining-bits)
(read-bytes-with-partial/macro stream bits :little-endian)
(loop for byte across buffer
for bit-shift from 0 by element-bits
do (incf result (ash byte bit-shift)))
(logior result
(ash partial-byte
(- bits remaining-bits))))))))
(defun read-bits (bits stream)
"Reads BITS bits from STREAM. If the STREAM is big-endian, the most
significant BITS bits will be read, otherwise, the least significant BITS bits
will be. The result is an integer of BITS bits."
(ecase (slot-value stream 'byte-order)
(:little-endian
(init-read stream)
(read-bits/little-endian bits stream))
(:big-endian
(init-read stream)
(read-bits/big-endian bits stream))))
(defun write-bits (n n-bits stream)
(when (= n-bits 0)
(return-from write-bits (values)))
(ecase (slot-value stream 'byte-order)
(:little-endian
(init-write stream)
(push-bits n (slot-value stream 'bits-left)
(slot-value stream 'last-byte))
(incf (slot-value stream 'bits-left) n-bits)
(loop while (>= (slot-value stream 'bits-left)
(slot-value stream 'element-bits))
do
(trace-write-byte (pop-bits/le (slot-value stream 'element-bits)
(slot-value stream 'last-byte))
(slot-value stream 'real-stream))
(decf (slot-value stream 'bits-left)
(slot-value stream 'element-bits))))
(:big-endian
(init-write stream)
(push-bits/le n n-bits
(slot-value stream 'last-byte))
(incf (slot-value stream 'bits-left) n-bits)
(loop while (>= (slot-value stream 'bits-left)
(slot-value stream 'element-bits))
do
(trace-write-byte (pop-bits (slot-value stream 'element-bits)
(slot-value stream 'bits-left)
(slot-value stream 'last-byte))
(slot-value stream 'real-stream))
(decf (slot-value stream 'bits-left)
(slot-value stream 'element-bits))))))
(defmethod stream-file-position ((stream bit-stream))
(cond ((slot-value stream 'real-stream)
(file-position (slot-value stream 'real-stream)))
(t (error "Not implemented for POSIX/Win32 descriptors."))))
(defmethod (setf stream-file-position) (position-spec (stream bit-stream))
(setf (slot-value stream 'bits-left) 0
(slot-value stream 'last-byte) 0)
(file-position (slot-value stream 'real-stream) position-spec))
(defmethod call-with-file-position ((stream bit-stream) position thunk)
(let ((bits-left (slot-value stream 'bits-left))
(last-byte (slot-value stream 'last-byte))
(last-op (slot-value stream 'last-op)))
(unwind-protect (call-next-method)
(setf (slot-value stream 'bits-left) bits-left
(slot-value stream 'last-byte) last-byte
(slot-value stream 'last-op) last-op))))