263ad95e4462fb1e6511597eed0403c804a7782c
[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 3u8
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 == 3u8
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 1u8 else 0u8
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 = 0u8
85 for b in bits.length.times do
86 if bits[b] then int |= 1u8 << (7 - 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 0x00u8
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 != 0u8
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 var arr = new Array[Bool]
168 for i in [7 .. 0].step(-1) do
169 arr.push(((int >> i) & 1u8) != 0u8)
170 end
171 return arr
172 end
173
174 # Read a null terminated string
175 #
176 # To be used with `Writer::write_string`.
177 #
178 # Returns a truncated string when an error is pending (`last_error != null`).
179 fun read_string: String
180 do
181 var buf = new FlatBuffer
182 loop
183 var byte = read_byte
184 if byte == null or byte == 0x00u8 then return buf.to_s
185 buf.bytes.add byte
186 end
187 end
188
189 # Read the length as a 64 bits integer, then the content of the block
190 #
191 # To be used with `Writer::write_block`.
192 #
193 # Returns a truncated string when an error is pending (`last_error != null`).
194 fun read_block: String
195 do
196 var length = read_int64
197 if length == 0 then return ""
198 return read(length)
199 end
200
201 # Read a floating point on 32 bits and return it as a `Float`
202 #
203 # Using this format may result in a loss of precision as it uses less bits
204 # than Nit `Float`.
205 #
206 # Returns `0.0` when an error is pending (`last_error != null`).
207 fun read_float: Float
208 do
209 if last_error != null then return 0.0
210
211 var b0 = read_byte
212 var b1 = read_byte
213 var b2 = read_byte
214 var b3 = read_byte
215
216 # Check for error, `last_error` is set by `read_byte`
217 if b0 == null or b1 == null or b2 == null or b3 == null then return 0.0
218
219 return native_read_float(b0, b1, b2, b3, big_endian)
220 end
221
222 # Utility for `read_float`
223 private fun native_read_float(b0, b1, b2, b3: Byte, big_endian: Bool): Float `{
224 union {
225 unsigned char b[4];
226 float val;
227 uint32_t conv;
228 } u;
229
230 u.b[0] = b0;
231 u.b[1] = b1;
232 u.b[2] = b2;
233 u.b[3] = b3;
234
235 if (big_endian)
236 u.conv = be32toh(u.conv);
237 else u.conv = le32toh(u.conv);
238
239 return u.val;
240 `}
241
242 # Read a floating point on 64 bits and return it as a `Float`
243 #
244 # Returns `0.0` when an error is pending (`last_error != null`).
245 fun read_double: Float
246 do
247 if last_error != null then return 0.0
248
249 var b0 = read_byte
250 var b1 = read_byte
251 var b2 = read_byte
252 var b3 = read_byte
253 var b4 = read_byte
254 var b5 = read_byte
255 var b6 = read_byte
256 var b7 = read_byte
257
258 # Check for error, `last_error` is set by `read_byte`
259 if b0 == null or b1 == null or b2 == null or b3 == null or
260 b4 == null or b5 == null or b6 == null or b7 == null then return 0.0
261
262 return native_read_double(b0, b1, b2, b3, b4, b5, b6, b7, big_endian)
263 end
264
265 # Utility for `read_double`
266 private fun native_read_double(b0, b1, b2, b3, b4, b5, b6, b7: Byte, big_endian: Bool): Float `{
267 union {
268 unsigned char b[8];
269 double val;
270 uint64_t conv;
271 } u;
272
273 u.b[0] = b0;
274 u.b[1] = b1;
275 u.b[2] = b2;
276 u.b[3] = b3;
277 u.b[4] = b4;
278 u.b[5] = b5;
279 u.b[6] = b6;
280 u.b[7] = b7;
281
282 if (big_endian)
283 u.conv = be64toh(u.conv);
284 else u.conv = le64toh(u.conv);
285
286 return u.val;
287 `}
288
289 # Read a signed integer on 64 bits and return is an `Int`
290 #
291 # Using this format may result in a loss of precision as the length of a
292 # Nit `Int` may be less than 64 bits on some platforms.
293 #
294 # Returns `0` when an error is pending (`last_error != null`).
295 fun read_int64: Int
296 do
297 if last_error != null then return 0
298
299 var b0 = read_byte
300 var b1 = read_byte
301 var b2 = read_byte
302 var b3 = read_byte
303 var b4 = read_byte
304 var b5 = read_byte
305 var b6 = read_byte
306 var b7 = read_byte
307
308 # Check for error, `last_error` is set by `read_byte`
309 if b0 == null or b1 == null or b2 == null or b3 == null or
310 b4 == null or b5 == null or b6 == null or b7 == null then return 0
311
312 return native_read_int64(b0, b1, b2, b3, b4, b5, b6, b7, big_endian)
313 end
314
315 # Utility for `read_int64`
316 private fun native_read_int64(b0, b1, b2, b3, b4, b5, b6, b7: Byte, big_endian: Bool): Int `{
317 union {
318 unsigned char b[8];
319 int64_t val;
320 uint64_t conv;
321 } u;
322
323 u.b[0] = b0;
324 u.b[1] = b1;
325 u.b[2] = b2;
326 u.b[3] = b3;
327 u.b[4] = b4;
328 u.b[5] = b5;
329 u.b[6] = b6;
330 u.b[7] = b7;
331
332 if (big_endian)
333 u.conv = be64toh(u.conv);
334 else u.conv = le64toh(u.conv);
335
336 return u.val;
337 `}
338 end
339
340 redef class Int
341 # Utility for `BinaryWriter`
342 private fun int64_byte_at(index: Int, big_endian: Bool): Byte `{
343 union {
344 unsigned char bytes[8];
345 int64_t val;
346 uint64_t conv;
347 } u;
348
349 u.val = self;
350
351 if (big_endian)
352 u.conv = htobe64(u.conv);
353 else u.conv = htole64(u.conv);
354
355 return u.bytes[index];
356 `}
357 end
358
359 redef class Float
360 # Utility for `BinaryWriter`
361 private fun float_byte_at(index: Int, big_endian: Bool): Byte `{
362 union {
363 unsigned char bytes[4];
364 float val;
365 uint32_t conv;
366 } u;
367
368 u.val = self;
369
370 if (big_endian)
371 u.conv = htobe32(u.conv);
372 else u.conv = htole32(u.conv);
373
374 return u.bytes[index];
375 `}
376
377 # Utility for `BinaryWriter`
378 private fun double_byte_at(index: Int, big_endian: Bool): Byte `{
379 union {
380 unsigned char bytes[8];
381 double val;
382 uint64_t conv;
383 } u;
384
385 u.val = self;
386
387 if (big_endian)
388 u.conv = htobe64(u.conv);
389 else u.conv = htole64(u.conv);
390
391 return u.bytes[index];
392 `}
393 end