lib: update other libs to use `self` from C and C++ code
[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 `text` as a null terminated string
90 #
91 # To be used with `Reader::read_string`.
92 #
93 # Require: `text` has no null bytes.
94 fun write_string(text: Text)
95 do
96 write text
97 write_byte 0x00
98 end
99
100 # Write the length as a 64 bits integer, then the content of `text`
101 #
102 # To be used with `Reader::read_block`.
103 #
104 # Compared to `write_string`, this method supports null bytes in `text`.
105 fun write_block(text: Text)
106 do
107 write_int64 text.length
108 write text
109 end
110
111 # Write a floating point `value` on 32 bits
112 #
113 # Using this format may result in a loss of precision as it uses less bits
114 # than Nit `Float`.
115 fun write_float(value: Float)
116 do
117 for i in [0..4[ do write_byte value.float_byte_at(i, big_endian)
118 end
119
120 # Write a floating point `value` on 64 bits
121 fun write_double(value: Float)
122 do
123 for i in [0..8[ do write_byte value.double_byte_at(i, big_endian)
124 end
125
126 # Write `value` as a signed integer on 64 bits
127 #
128 # Using this format may result in a loss of precision as the length of a
129 # Nit `Int` may be more than 64 bits on some platforms.
130 fun write_int64(value: Int)
131 do
132 for i in [0..8[ do write_byte value.int64_byte_at(i, big_endian)
133 end
134
135 # TODO:
136 #
137 # fun write_int8
138 # fun write_uint8 # No need for this one, it is the current `read_char`
139 # fun write_int16
140 # fun write_uint16
141 # fun write_int32
142 # fun write_uint32
143 # fun write_uint64
144 # fun write_long_double?
145 end
146
147 redef abstract class Reader
148 super BinaryStream
149
150 # Read a single byte and return `true` if its value is different than 0
151 fun read_bool: Bool do return read_byte != 0
152
153 # Get an `Array` of 8 `Bool` by reading a single byte
154 #
155 # To be used with `BinaryWriter::write_bits`.
156 fun read_bits: Array[Bool]
157 do
158 var int = read_byte
159 if int == null then return new Array[Bool]
160 return [for b in 8.times do int.bin_and(2**b) > 0]
161 end
162
163 # Read a null terminated string
164 #
165 # To be used with `Writer::write_string`.
166 fun read_string: String
167 do
168 var buf = new FlatBuffer
169 loop
170 var byte = read_byte
171 if byte == 0x00 then return buf.to_s
172 buf.chars.add byte.ascii
173 end
174 end
175
176 # Read the length as a 64 bits integer, then the content of the block
177 #
178 # To be used with `Writer::write_block`.
179 fun read_block: String
180 do
181 var length = read_int64
182 if length == 0 then return ""
183 return read(length)
184 end
185
186 # Read a floating point on 32 bits and return it as a `Float`
187 #
188 # Using this format may result in a loss of precision as it uses less bits
189 # than Nit `Float`.
190 fun read_float: Float
191 do
192 if last_error != null then return 0.0
193
194 var b0 = read_byte
195 var b1 = read_byte
196 var b2 = read_byte
197 var b3 = read_byte
198
199 # Check for error, `last_error` is set by `read_byte`
200 if b0 == null or b1 == null or b2 == null or b3 == null then return 0.0
201
202 return native_read_float(b0, b1, b2, b3, big_endian)
203 end
204
205 # Utility for `read_float`
206 private fun native_read_float(b0, b1, b2, b3: Int, big_endian: Bool): Float `{
207 union {
208 unsigned char b[4];
209 float val;
210 uint32_t conv;
211 } u;
212
213 u.b[0] = b0;
214 u.b[1] = b1;
215 u.b[2] = b2;
216 u.b[3] = b3;
217
218 if (big_endian)
219 u.conv = be32toh(u.conv);
220 else u.conv = le32toh(u.conv);
221
222 return u.val;
223 `}
224
225 # Read a floating point on 64 bits and return it as a `Float`
226 fun read_double: Float
227 do
228 if last_error != null then return 0.0
229
230 var b0 = read_byte
231 var b1 = read_byte
232 var b2 = read_byte
233 var b3 = read_byte
234 var b4 = read_byte
235 var b5 = read_byte
236 var b6 = read_byte
237 var b7 = read_byte
238
239 # Check for error, `last_error` is set by `read_byte`
240 if b0 == null or b1 == null or b2 == null or b3 == null or
241 b4 == null or b5 == null or b6 == null or b7 == null then return 0.0
242
243 return native_read_double(b0, b1, b2, b3, b4, b5, b6, b7, big_endian)
244 end
245
246 # Utility for `read_double`
247 private fun native_read_double(b0, b1, b2, b3, b4, b5, b6, b7: Int, big_endian: Bool): Float `{
248 union {
249 unsigned char b[8];
250 double val;
251 uint64_t conv;
252 } u;
253
254 u.b[0] = b0;
255 u.b[1] = b1;
256 u.b[2] = b2;
257 u.b[3] = b3;
258 u.b[4] = b4;
259 u.b[5] = b5;
260 u.b[6] = b6;
261 u.b[7] = b7;
262
263 if (big_endian)
264 u.conv = be64toh(u.conv);
265 else u.conv = le64toh(u.conv);
266
267 return u.val;
268 `}
269
270 # Read a signed integer on 64 bits and return is an `Int`
271 #
272 # Using this format may result in a loss of precision as the length of a
273 # Nit `Int` may be less than 64 bits on some platforms.
274 fun read_int64: Int
275 do
276 if last_error != null then return 0
277
278 var b0 = read_byte
279 var b1 = read_byte
280 var b2 = read_byte
281 var b3 = read_byte
282 var b4 = read_byte
283 var b5 = read_byte
284 var b6 = read_byte
285 var b7 = read_byte
286
287 # Check for error, `last_error` is set by `read_byte`
288 if b0 == null or b1 == null or b2 == null or b3 == null or
289 b4 == null or b5 == null or b6 == null or b7 == null then return 0
290
291 return native_read_int64(b0, b1, b2, b3, b4, b5, b6, b7, big_endian)
292 end
293
294 # Utility for `read_int64`
295 private fun native_read_int64(b0, b1, b2, b3, b4, b5, b6, b7: Int, big_endian: Bool): Int `{
296 union {
297 unsigned char b[8];
298 int64_t val;
299 uint64_t conv;
300 } u;
301
302 u.b[0] = b0;
303 u.b[1] = b1;
304 u.b[2] = b2;
305 u.b[3] = b3;
306 u.b[4] = b4;
307 u.b[5] = b5;
308 u.b[6] = b6;
309 u.b[7] = b7;
310
311 if (big_endian)
312 u.conv = be64toh(u.conv);
313 else u.conv = le64toh(u.conv);
314
315 return u.val;
316 `}
317 end
318
319 redef class Int
320 # Utility for `BinaryWriter`
321 private fun int64_byte_at(index: Int, big_endian: Bool): Int `{
322 union {
323 unsigned char bytes[8];
324 int64_t val;
325 uint64_t conv;
326 } u;
327
328 u.val = self;
329
330 if (big_endian)
331 u.conv = htobe64(u.conv);
332 else u.conv = htole64(u.conv);
333
334 return u.bytes[index];
335 `}
336 end
337
338 redef class Float
339 # Utility for `BinaryWriter`
340 private fun float_byte_at(index: Int, big_endian: Bool): Int `{
341 union {
342 unsigned char bytes[4];
343 float val;
344 uint32_t conv;
345 } u;
346
347 u.val = self;
348
349 if (big_endian)
350 u.conv = htobe32(u.conv);
351 else u.conv = htole32(u.conv);
352
353 return u.bytes[index];
354 `}
355
356 # Utility for `BinaryWriter`
357 private fun double_byte_at(index: Int, big_endian: Bool): Int `{
358 union {
359 unsigned char bytes[8];
360 double val;
361 uint64_t conv;
362 } u;
363
364 u.val = self;
365
366 if (big_endian)
367 u.conv = htobe64(u.conv);
368 else u.conv = htole64(u.conv);
369
370 return u.bytes[index];
371 `}
372 end