Merge: Manage errors on reading binary data
[nit.git] / lib / binary / binary.nit
1 # This file is part of NIT (http://www.nitlanguage.org).
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 # Add reading and writing binary services
16 #
17 # ~~~
18 # var w = new FileWriter.open("/tmp/data.bin")
19 # w.write "hello"
20 # w.write_int64 123456789
21 # w.write_byte 3
22 # w.write_float 1.25
23 # w.write_double 1.234567
24 # w.write_bits(true, false, true)
25 # assert w.last_error == null
26 # w.close
27 #
28 # var r = new FileReader.open("/tmp/data.bin")
29 # assert r.read(5) == "hello"
30 # assert r.read_int64 == 123456789
31 # assert r.read_byte == 3
32 # assert r.read_float == 1.25
33 # assert r.read_double == 1.234567
34 #
35 # var bits = r.read_bits
36 # assert bits[0] and not bits[1] and bits[2]
37 #
38 # assert r.last_error == null
39 # r.close
40 # ~~~
41 module binary
42
43 in "C" `{
44 #include <inttypes.h>
45 #include <endian.h>
46
47 // Android compatibility
48 #ifndef be32toh
49 #define be32toh(val) betoh32(val)
50 #define le32toh(val) letoh32(val)
51 #endif
52
53 #ifndef be64toh
54 #define be64toh(val) betoh64(val)
55 #define le64toh(val) letoh64(val)
56 #endif
57 `}
58
59 # A stream of binary data
60 abstract class BinaryStream
61 super Stream
62
63 # Use the big-endian convention? otherwise use little-endian.
64 #
65 # By default, `true` to use big-endian.
66 var big_endian = true is writable
67 end
68
69 redef abstract class Writer
70 super BinaryStream
71
72 # Write a boolean `value` on a byte, using 0 for `false` and 1 for `true`
73 fun write_bool(value: Bool) do write_byte if value then 1 else 0
74
75 # Write up to 8 `Bool` in a byte
76 #
77 # To be used with `BinaryReader::read_bits`.
78 #
79 # Ensure: `bits.length <= 8`
80 fun write_bits(bits: Bool...)
81 do
82 assert bits.length <= 8
83
84 var int = 0
85 for b in bits.length.times do
86 if bits[b] then int += 2**b
87 end
88
89 write_byte int
90 end
91
92 # Write `text` as a null terminated string
93 #
94 # To be used with `Reader::read_string`.
95 #
96 # Require: `text` has no null bytes.
97 fun write_string(text: Text)
98 do
99 write text
100 write_byte 0x00
101 end
102
103 # Write the length as a 64 bits integer, then the content of `text`
104 #
105 # To be used with `Reader::read_block`.
106 #
107 # Compared to `write_string`, this method supports null bytes in `text`.
108 fun write_block(text: Text)
109 do
110 write_int64 text.length
111 write text
112 end
113
114 # Write a floating point `value` on 32 bits
115 #
116 # Using this format may result in a loss of precision as it uses less bits
117 # than Nit `Float`.
118 fun write_float(value: Float)
119 do
120 for i in [0..4[ do write_byte value.float_byte_at(i, big_endian)
121 end
122
123 # Write a floating point `value` on 64 bits
124 fun write_double(value: Float)
125 do
126 for i in [0..8[ do write_byte value.double_byte_at(i, big_endian)
127 end
128
129 # Write `value` as a signed integer on 64 bits
130 #
131 # Using this format may result in a loss of precision as the length of a
132 # Nit `Int` may be more than 64 bits on some platforms.
133 fun write_int64(value: Int)
134 do
135 for i in [0..8[ do write_byte value.int64_byte_at(i, big_endian)
136 end
137
138 # TODO:
139 #
140 # fun write_int8
141 # fun write_uint8 # No need for this one, it is the current `read_char`
142 # fun write_int16
143 # fun write_uint16
144 # fun write_int32
145 # fun write_uint32
146 # fun write_uint64
147 # fun write_long_double?
148 end
149
150 redef abstract class Reader
151 super BinaryStream
152
153 # Read a single byte and return `true` if its value is different than 0
154 #
155 # Returns `false` when an error is pending (`last_error != null`).
156 fun read_bool: Bool do return read_byte != 0
157
158 # Get an `Array` of 8 `Bool` by reading a single byte
159 #
160 # To be used with `BinaryWriter::write_bits`.
161 #
162 # Returns an array of `false` when an error is pending (`last_error != null`).
163 fun read_bits: Array[Bool]
164 do
165 var int = read_byte
166 if int == null then return new Array[Bool]
167 return [for b in 8.times do int.bin_and(2**b) > 0]
168 end
169
170 # Read a null terminated string
171 #
172 # To be used with `Writer::write_string`.
173 #
174 # Returns a truncated string when an error is pending (`last_error != null`).
175 fun read_string: String
176 do
177 var buf = new FlatBuffer
178 loop
179 var byte = read_byte
180 if byte == null or byte == 0x00 then return buf.to_s
181 buf.chars.add byte.ascii
182 end
183 end
184
185 # Read the length as a 64 bits integer, then the content of the block
186 #
187 # To be used with `Writer::write_block`.
188 #
189 # Returns a truncated string when an error is pending (`last_error != null`).
190 fun read_block: String
191 do
192 var length = read_int64
193 if length == 0 then return ""
194 return read(length)
195 end
196
197 # Read a floating point on 32 bits and return it as a `Float`
198 #
199 # Using this format may result in a loss of precision as it uses less bits
200 # than Nit `Float`.
201 #
202 # Returns `0.0` when an error is pending (`last_error != null`).
203 fun read_float: Float
204 do
205 if last_error != null then return 0.0
206
207 var b0 = read_byte
208 var b1 = read_byte
209 var b2 = read_byte
210 var b3 = read_byte
211
212 # Check for error, `last_error` is set by `read_byte`
213 if b0 == null or b1 == null or b2 == null or b3 == null then return 0.0
214
215 return native_read_float(b0, b1, b2, b3, big_endian)
216 end
217
218 # Utility for `read_float`
219 private fun native_read_float(b0, b1, b2, b3: Int, big_endian: Bool): Float `{
220 union {
221 unsigned char b[4];
222 float val;
223 uint32_t conv;
224 } u;
225
226 u.b[0] = b0;
227 u.b[1] = b1;
228 u.b[2] = b2;
229 u.b[3] = b3;
230
231 if (big_endian)
232 u.conv = be32toh(u.conv);
233 else u.conv = le32toh(u.conv);
234
235 return u.val;
236 `}
237
238 # Read a floating point on 64 bits and return it as a `Float`
239 #
240 # Returns `0.0` when an error is pending (`last_error != null`).
241 fun read_double: Float
242 do
243 if last_error != null then return 0.0
244
245 var b0 = read_byte
246 var b1 = read_byte
247 var b2 = read_byte
248 var b3 = read_byte
249 var b4 = read_byte
250 var b5 = read_byte
251 var b6 = read_byte
252 var b7 = read_byte
253
254 # Check for error, `last_error` is set by `read_byte`
255 if b0 == null or b1 == null or b2 == null or b3 == null or
256 b4 == null or b5 == null or b6 == null or b7 == null then return 0.0
257
258 return native_read_double(b0, b1, b2, b3, b4, b5, b6, b7, big_endian)
259 end
260
261 # Utility for `read_double`
262 private fun native_read_double(b0, b1, b2, b3, b4, b5, b6, b7: Int, big_endian: Bool): Float `{
263 union {
264 unsigned char b[8];
265 double val;
266 uint64_t conv;
267 } u;
268
269 u.b[0] = b0;
270 u.b[1] = b1;
271 u.b[2] = b2;
272 u.b[3] = b3;
273 u.b[4] = b4;
274 u.b[5] = b5;
275 u.b[6] = b6;
276 u.b[7] = b7;
277
278 if (big_endian)
279 u.conv = be64toh(u.conv);
280 else u.conv = le64toh(u.conv);
281
282 return u.val;
283 `}
284
285 # Read a signed integer on 64 bits and return is an `Int`
286 #
287 # Using this format may result in a loss of precision as the length of a
288 # Nit `Int` may be less than 64 bits on some platforms.
289 #
290 # Returns `0` when an error is pending (`last_error != null`).
291 fun read_int64: Int
292 do
293 if last_error != null then return 0
294
295 var b0 = read_byte
296 var b1 = read_byte
297 var b2 = read_byte
298 var b3 = read_byte
299 var b4 = read_byte
300 var b5 = read_byte
301 var b6 = read_byte
302 var b7 = read_byte
303
304 # Check for error, `last_error` is set by `read_byte`
305 if b0 == null or b1 == null or b2 == null or b3 == null or
306 b4 == null or b5 == null or b6 == null or b7 == null then return 0
307
308 return native_read_int64(b0, b1, b2, b3, b4, b5, b6, b7, big_endian)
309 end
310
311 # Utility for `read_int64`
312 private fun native_read_int64(b0, b1, b2, b3, b4, b5, b6, b7: Int, big_endian: Bool): Int `{
313 union {
314 unsigned char b[8];
315 int64_t val;
316 uint64_t conv;
317 } u;
318
319 u.b[0] = b0;
320 u.b[1] = b1;
321 u.b[2] = b2;
322 u.b[3] = b3;
323 u.b[4] = b4;
324 u.b[5] = b5;
325 u.b[6] = b6;
326 u.b[7] = b7;
327
328 if (big_endian)
329 u.conv = be64toh(u.conv);
330 else u.conv = le64toh(u.conv);
331
332 return u.val;
333 `}
334 end
335
336 redef class Int
337 # Utility for `BinaryWriter`
338 private fun int64_byte_at(index: Int, big_endian: Bool): Int `{
339 union {
340 unsigned char bytes[8];
341 int64_t val;
342 uint64_t conv;
343 } u;
344
345 u.val = self;
346
347 if (big_endian)
348 u.conv = htobe64(u.conv);
349 else u.conv = htole64(u.conv);
350
351 return u.bytes[index];
352 `}
353 end
354
355 redef class Float
356 # Utility for `BinaryWriter`
357 private fun float_byte_at(index: Int, big_endian: Bool): Int `{
358 union {
359 unsigned char bytes[4];
360 float val;
361 uint32_t conv;
362 } u;
363
364 u.val = self;
365
366 if (big_endian)
367 u.conv = htobe32(u.conv);
368 else u.conv = htole32(u.conv);
369
370 return u.bytes[index];
371 `}
372
373 # Utility for `BinaryWriter`
374 private fun double_byte_at(index: Int, big_endian: Bool): Int `{
375 union {
376 unsigned char bytes[8];
377 double val;
378 uint64_t conv;
379 } u;
380
381 u.val = self;
382
383 if (big_endian)
384 u.conv = htobe64(u.conv);
385 else u.conv = htole64(u.conv);
386
387 return u.bytes[index];
388 `}
389 end