tests: add test_keep_going.nit
[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 be64toh
49 #define be64toh(val) betoh64(val)
50 #endif
51 #ifndef le64toh
52 #define le64toh(val) letoh64(val)
53 #endif
54 `}
55
56 # A stream of binary data
57 abstract class BinaryStream
58 super Stream
59
60 # Use the big-endian convention? otherwise use little-endian.
61 #
62 # By default, `true` to use big-endian.
63 var big_endian = true is writable
64 end
65
66 redef abstract class Writer
67 super BinaryStream
68
69 # Write a boolean `value` on a byte, using 0 for `false` and 1 for `true`
70 fun write_bool(value: Bool) do write_byte if value then 1 else 0
71
72 # Write up to 8 `Bool` in a byte
73 #
74 # To be used with `BinaryReader::read_bits`.
75 #
76 # Ensure: `bits.length <= 8`
77 fun write_bits(bits: Bool...)
78 do
79 assert bits.length <= 8
80
81 var int = 0
82 for b in bits.length.times do
83 if bits[b] then int += 2**b
84 end
85
86 write_byte int
87 end
88
89 # Write a floating point `value` on 32 bits
90 #
91 # Using this format may result in a loss of precision as it uses less bits
92 # than Nit `Float`.
93 fun write_float(value: Float)
94 do
95 for i in [0..4[ do write_byte value.float_byte_at(i, big_endian)
96 end
97
98 # Write a floating point `value` on 64 bits
99 fun write_double(value: Float)
100 do
101 for i in [0..8[ do write_byte value.double_byte_at(i, big_endian)
102 end
103
104 # Write `value` as a signed integer on 64 bits
105 #
106 # Using this format may result in a loss of precision as the length of a
107 # Nit `Int` may be more than 64 bits on some platforms.
108 fun write_int64(value: Int)
109 do
110 for i in [0..8[ do write_byte value.int64_byte_at(i, big_endian)
111 end
112
113 # TODO:
114 #
115 # fun write_int8
116 # fun write_uint8 # No need for this one, it is the current `read_char`
117 # fun write_int16
118 # fun write_uint16
119 # fun write_int32
120 # fun write_uint32
121 # fun write_uint64
122 # fun write_long_double?
123 end
124
125 redef abstract class Reader
126 super BinaryStream
127
128 # Read a single byte and return `true` if its value is different than 0
129 fun read_bool: Bool do return read_byte != 0
130
131 # Get an `Array` of 8 `Bool` by reading a single byte
132 #
133 # To be used with `BinaryWriter::write_bits`.
134 fun read_bits: Array[Bool]
135 do
136 var int = read_byte
137 if int == null then return new Array[Bool]
138 return [for b in 8.times do int.bin_and(2**b) > 0]
139 end
140
141 # Read a floating point on 32 bits and return it as a `Float`
142 #
143 # Using this format may result in a loss of precision as it uses less bits
144 # than Nit `Float`.
145 fun read_float: Float
146 do
147 if last_error != null then return 0.0
148
149 var b0 = read_byte
150 var b1 = read_byte
151 var b2 = read_byte
152 var b3 = read_byte
153
154 # Check for error, `last_error` is set by `read_byte`
155 if b0 == null or b1 == null or b2 == null or b3 == null then return 0.0
156
157 return native_read_float(b0, b1, b2, b3, big_endian)
158 end
159
160 # Utility for `read_float`
161 private fun native_read_float(b0, b1, b2, b3: Int, big_endian: Bool): Float `{
162 union {
163 unsigned char b[4];
164 float val;
165 uint32_t conv;
166 } u;
167
168 u.b[0] = b0;
169 u.b[1] = b1;
170 u.b[2] = b2;
171 u.b[3] = b3;
172
173 if (big_endian)
174 u.conv = be32toh(u.conv);
175 else u.conv = le32toh(u.conv);
176
177 return u.val;
178 `}
179
180 # Read a floating point on 64 bits and return it as a `Float`
181 fun read_double: Float
182 do
183 if last_error != null then return 0.0
184
185 var b0 = read_byte
186 var b1 = read_byte
187 var b2 = read_byte
188 var b3 = read_byte
189 var b4 = read_byte
190 var b5 = read_byte
191 var b6 = read_byte
192 var b7 = read_byte
193
194 # Check for error, `last_error` is set by `read_byte`
195 if b0 == null or b1 == null or b2 == null or b3 == null or
196 b4 == null or b5 == null or b6 == null or b7 == null then return 0.0
197
198 return native_read_double(b0, b1, b2, b3, b4, b5, b6, b7, big_endian)
199 end
200
201 # Utility for `read_double`
202 private fun native_read_double(b0, b1, b2, b3, b4, b5, b6, b7: Int, big_endian: Bool): Float `{
203 union {
204 unsigned char b[8];
205 double val;
206 uint64_t conv;
207 } u;
208
209 u.b[0] = b0;
210 u.b[1] = b1;
211 u.b[2] = b2;
212 u.b[3] = b3;
213 u.b[4] = b4;
214 u.b[5] = b5;
215 u.b[6] = b6;
216 u.b[7] = b7;
217
218 if (big_endian)
219 u.conv = be64toh(u.conv);
220 else u.conv = le64toh(u.conv);
221
222 return u.val;
223 `}
224
225 # Read a signed integer on 64 bits and return is an `Int`
226 #
227 # Using this format may result in a loss of precision as the length of a
228 # Nit `Int` may be less than 64 bits on some platforms.
229 fun read_int64: Int
230 do
231 if last_error != null then return 0
232
233 var b0 = read_byte
234 var b1 = read_byte
235 var b2 = read_byte
236 var b3 = read_byte
237 var b4 = read_byte
238 var b5 = read_byte
239 var b6 = read_byte
240 var b7 = read_byte
241
242 # Check for error, `last_error` is set by `read_byte`
243 if b0 == null or b1 == null or b2 == null or b3 == null or
244 b4 == null or b5 == null or b6 == null or b7 == null then return 0
245
246 return native_read_int64(b0, b1, b2, b3, b4, b5, b6, b7, big_endian)
247 end
248
249 # Utility for `read_int64`
250 private fun native_read_int64(b0, b1, b2, b3, b4, b5, b6, b7: Int, big_endian: Bool): Int `{
251 union {
252 unsigned char b[8];
253 int64_t val;
254 uint64_t conv;
255 } u;
256
257 u.b[0] = b0;
258 u.b[1] = b1;
259 u.b[2] = b2;
260 u.b[3] = b3;
261 u.b[4] = b4;
262 u.b[5] = b5;
263 u.b[6] = b6;
264 u.b[7] = b7;
265
266 if (big_endian)
267 u.conv = be64toh(u.conv);
268 else u.conv = le64toh(u.conv);
269
270 return u.val;
271 `}
272 end
273
274 redef class Int
275 # Utility for `BinaryWriter`
276 private fun int64_byte_at(index: Int, big_endian: Bool): Int `{
277 union {
278 unsigned char bytes[8];
279 int64_t val;
280 uint64_t conv;
281 } u;
282
283 u.val = recv;
284
285 if (big_endian)
286 u.conv = htobe64(u.conv);
287 else u.conv = htole64(u.conv);
288
289 return u.bytes[index];
290 `}
291 end
292
293 redef class Float
294 # Utility for `BinaryWriter`
295 private fun float_byte_at(index: Int, big_endian: Bool): Int `{
296 union {
297 unsigned char bytes[4];
298 float val;
299 uint32_t conv;
300 } u;
301
302 u.val = recv;
303
304 if (big_endian)
305 u.conv = htobe32(u.conv);
306 else u.conv = htole32(u.conv);
307
308 return u.bytes[index];
309 `}
310
311 # Utility for `BinaryWriter`
312 private fun double_byte_at(index: Int, big_endian: Bool): Int `{
313 union {
314 unsigned char bytes[8];
315 double val;
316 uint64_t conv;
317 } u;
318
319 u.val = recv;
320
321 if (big_endian)
322 u.conv = htobe64(u.conv);
323 else u.conv = htole64(u.conv);
324
325 return u.bytes[index];
326 `}
327 end