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