1 # This file is part of NIT (http://www.nitlanguage.org).
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
7 # http://www.apache.org/licenses/LICENSE-2.0
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.
15 # Add reading and writing binary services
18 # var w = new FileWriter.open("/tmp/data.bin")
20 # w.write_int64 123456789
23 # w.write_double 1.234567
24 # w.write_bits(true, false, true)
25 # assert w.last_error == null
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
35 # var bits = r.read_bits
36 # assert bits[0] and not bits[1] and bits[2]
38 # assert r.last_error == null
47 // Android compatibility
49 #define be32toh(val) betoh32(val)
50 #define le32toh(val) letoh32(val)
54 #define be64toh(val) betoh64(val)
55 #define le64toh(val) letoh64(val)
59 # A stream of binary data
60 abstract class BinaryStream
63 # Use the big-endian convention? otherwise use little-endian.
65 # By default, `true` to use big-endian.
66 var big_endian
= true is writable
69 redef abstract class Writer
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
75 # Write up to 8 `Bool` in a byte
77 # To be used with `BinaryReader::read_bits`.
79 # Ensure: `bits.length <= 8`
80 fun write_bits
(bits
: Bool...)
82 assert bits
.length
<= 8
85 for b
in bits
.length
.times
do
86 if bits
[b
] then int
|= 1u8
<< (7 - b
)
92 # Write `text` as a null terminated string
94 # To be used with `Reader::read_string`.
96 # Require: `text` has no null bytes.
97 fun write_string
(text
: Text)
103 # Write the length as a 64 bits integer, then the content of `text`
105 # To be used with `Reader::read_block`.
107 # Compared to `write_string`, this method supports null bytes in `text`.
108 fun write_block
(text
: Text)
110 write_int64 text
.length
114 # Write a floating point `value` on 32 bits
116 # Using this format may result in a loss of precision as it uses less bits
118 fun write_float
(value
: Float)
120 for i
in [0..4[ do write_byte value
.float_byte_at
(i
, big_endian
)
123 # Write a floating point `value` on 64 bits
124 fun write_double
(value
: Float)
126 for i
in [0..8[ do write_byte value
.double_byte_at
(i
, big_endian
)
129 # Write `value` as a signed integer on 64 bits
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)
135 for i
in [0..8[ do write_byte value
.int64_byte_at
(i
, big_endian
)
141 # fun write_uint8 # No need for this one, it is the current `read_char`
147 # fun write_long_double?
150 redef abstract class Reader
153 # Read a single byte and return `true` if its value is different than 0
155 # Returns `false` when an error is pending (`last_error != null`).
156 fun read_bool
: Bool do return read_byte
!= 0u8
158 # Get an `Array` of 8 `Bool` by reading a single byte
160 # To be used with `BinaryWriter::write_bits`.
162 # Returns an array of `false` when an error is pending (`last_error != null`).
163 fun read_bits
: Array[Bool]
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
)
174 # Read a null terminated string
176 # To be used with `Writer::write_string`.
178 # Returns a truncated string when an error is pending (`last_error != null`).
179 fun read_string
: String
181 var buf
= new Bytes.empty
184 if byte
== null or byte
== 0u8
then
191 # Read the length as a 64 bits integer, then the content of the block
193 # To be used with `Writer::write_block`.
195 # Returns a truncated string when an error is pending (`last_error != null`).
196 fun read_block
: String
198 var length
= read_int64
199 if length
== 0 then return ""
203 # Read a floating point on 32 bits and return it as a `Float`
205 # Using this format may result in a loss of precision as it uses less bits
208 # Returns `0.0` when an error is pending (`last_error != null`).
209 fun read_float
: Float
211 if last_error
!= null then return 0.0
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
221 return native_read_float
(b0
, b1
, b2
, b3
, big_endian
)
224 # Utility for `read_float`
225 private fun native_read_float
(b0
, b1
, b2
, b3
: Byte, big_endian
: Bool): Float `{
238 u.conv = be32toh(u.conv);
239 else u.conv = le32toh(u.conv);
244 # Read a floating point on 64 bits and return it as a `Float`
246 # Returns `0.0` when an error is pending (`last_error != null`).
247 fun read_double
: Float
249 if last_error
!= null then return 0.0
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
264 return native_read_double
(b0
, b1
, b2
, b3
, b4
, b5
, b6
, b7
, big_endian
)
267 # Utility for `read_double`
268 private fun native_read_double
(b0
, b1
, b2
, b3
, b4
, b5
, b6
, b7
: Byte, big_endian
: Bool): Float `{
285 u.conv = be64toh(u.conv);
286 else u.conv = le64toh(u.conv);
291 # Read a signed integer on 64 bits and return is an `Int`
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.
296 # Returns `0` when an error is pending (`last_error != null`).
299 if last_error
!= null then return 0
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
314 return native_read_int64
(b0
, b1
, b2
, b3
, b4
, b5
, b6
, b7
, big_endian
)
317 # Utility for `read_int64`
318 private fun native_read_int64
(b0
, b1
, b2
, b3
, b4
, b5
, b6
, b7
: Byte, big_endian
: Bool): Int `{
335 u.conv = be64toh(u.conv);
336 else u.conv = le64toh(u.conv);
343 # Utility for `BinaryWriter`
344 private fun int64_byte_at
(index
: Int, big_endian
: Bool): Byte `{
346 unsigned char bytes[8];
354 u.conv = htobe64(u.conv);
355 else u.conv = htole64(u.conv);
357 return u.bytes[index];
362 # Utility for `BinaryWriter`
363 private fun float_byte_at
(index
: Int, big_endian
: Bool): Byte `{
365 unsigned char bytes[4];
373 u.conv = htobe32(u.conv);
374 else u.conv = htole32(u.conv);
376 return u.bytes[index];
379 # Utility for `BinaryWriter`
380 private fun double_byte_at
(index
: Int, big_endian
: Bool): Byte `{
382 unsigned char bytes[8];
390 u.conv = htobe64(u.conv);
391 else u.conv = htole64(u.conv);
393 return u.bytes[index];