libbin 1.0.8 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/ext/libbin/data_types.c +107 -16
- data/ext/libbin/data_types.h +2 -1
- data/ext/libbin/libbin_c.c +1145 -679
- data/lib/libbin/data_types.rb +655 -132
- data/lib/libbin.rb +83 -17
- data/libbin.gemspec +1 -1
- metadata +2 -3
- data/lib/libbin/alignment.rb +0 -14
data/lib/libbin/data_types.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
1
|
module LibBin
|
2
2
|
|
3
|
+
# Refinement to Range allowing reducing ranges through the + operator
|
3
4
|
module RangeRefinement
|
4
5
|
refine Range do
|
6
|
+
# Union of two ranges
|
7
|
+
# @return [Range]
|
5
8
|
def +(other)
|
6
9
|
return other.dup unless min
|
7
10
|
return self.dup unless other.min
|
@@ -11,187 +14,707 @@ module LibBin
|
|
11
14
|
end
|
12
15
|
end
|
13
16
|
|
14
|
-
|
17
|
+
# Classs that can be used to get the shape of a {Structure} or of a {Structure::Scalar}.
|
18
|
+
class DataRange
|
15
19
|
using RangeRefinement
|
16
20
|
attr_reader :range
|
17
|
-
attr_reader :members
|
18
|
-
|
19
|
-
def method_missing(m, *arg, &block)
|
20
|
-
return @members[m] if @members && @members[m]
|
21
|
-
super
|
22
|
-
end
|
23
21
|
|
22
|
+
# @overload initialize(min, max)
|
23
|
+
# Create a new shape starting and +min+ and ending at max.
|
24
|
+
# This shape has no members.
|
25
|
+
# @param min [Integer] start of the shape
|
26
|
+
# @param max [Integer] end of the shape
|
27
|
+
# @return [DataShape] a new DataShape
|
28
|
+
# @overload initialize(members)
|
29
|
+
# Creates a new shape by reducing the shape of it's memebers.
|
30
|
+
# @param members [DataShape] the members composing the shape
|
31
|
+
# @return [DataShape] a new DataShape
|
24
32
|
def initialize(*args)
|
25
33
|
if args.length == 2
|
26
34
|
@range = Range::new(args[0], args[1])
|
27
|
-
@members = nil
|
28
35
|
else
|
29
|
-
|
30
|
-
|
31
|
-
@range = @members.values.compact.collect(&:range).reduce(:+)
|
36
|
+
if args[0].kind_of?(Hash)
|
37
|
+
@range = args[0].values.compact.collect(&:range).reduce(:+)
|
32
38
|
else
|
33
|
-
@range =
|
39
|
+
@range = args[0].compact.collect(&:range).reduce(:+)
|
34
40
|
end
|
35
41
|
end
|
36
42
|
end
|
37
43
|
|
44
|
+
# Return the beginning of the shape
|
45
|
+
# @return [Integer]
|
38
46
|
def first
|
39
47
|
@range.first
|
40
48
|
end
|
41
49
|
|
50
|
+
# Return the end of the shape
|
51
|
+
# @return [Integer]
|
42
52
|
def last
|
43
53
|
@range.last
|
44
54
|
end
|
45
55
|
|
56
|
+
# Return the size of the shape
|
57
|
+
# @return [Integer]
|
46
58
|
def size
|
47
59
|
@range.size
|
48
60
|
end
|
49
61
|
|
50
62
|
end
|
51
63
|
|
52
|
-
class
|
64
|
+
# Default class used to get the shape of a {Structure} or of a {Structure::Scalar}.
|
65
|
+
# It maintains a memory of it's members
|
66
|
+
class DataShape # < DataRange
|
53
67
|
using RangeRefinement
|
54
|
-
attr_reader :
|
68
|
+
attr_reader :members
|
55
69
|
|
70
|
+
# Add method readers for members
|
71
|
+
def method_missing(m, *arg, &block)
|
72
|
+
return @members[m] if @members && @members[m]
|
73
|
+
super
|
74
|
+
end
|
75
|
+
|
76
|
+
# @overload initialize(min, max)
|
77
|
+
# Create a new shape starting and +min+ and ending at max.
|
78
|
+
# This shape has no members.
|
79
|
+
# @param min [Integer] start of the shape
|
80
|
+
# @param max [Integer] end of the shape
|
81
|
+
# @return [DataShape] a new DataShape
|
82
|
+
# @overload initialize(members)
|
83
|
+
# Creates a new shape by reducing the shape of it's memeber.
|
84
|
+
# Members can be accessed through {members}.
|
85
|
+
# @param members [DataShape] the members composing the shape
|
86
|
+
# @return [DataShape] a new DataShape
|
56
87
|
def initialize(*args)
|
57
|
-
if args.length ==
|
58
|
-
@
|
59
|
-
|
60
|
-
|
61
|
-
|
88
|
+
if args.length == 1
|
89
|
+
@members = args[0]
|
90
|
+
end
|
91
|
+
super
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
# @abstract This class is used to describe a binary data structure
|
97
|
+
# composed of different fields.
|
98
|
+
class Structure
|
99
|
+
|
100
|
+
# @!parse
|
101
|
+
# # Field class that is instantiated to describe a structure field.
|
102
|
+
# # Instances are immutable.
|
103
|
+
# class Field
|
104
|
+
# attr_reader :name
|
105
|
+
# attr_reader :type
|
106
|
+
# attr_reader :length
|
107
|
+
# attr_reader :count
|
108
|
+
# attr_reader :offset
|
109
|
+
# attr_reader :condition
|
110
|
+
# attr_reader :align
|
111
|
+
# attr_reader :expect
|
112
|
+
#
|
113
|
+
# # @method relative_offset?
|
114
|
+
# # Returns +true+ if the field offset should be relative to its parent.
|
115
|
+
# # @return [Boolean] field offset is relative
|
116
|
+
#
|
117
|
+
# # @method sequence?
|
118
|
+
# # Returns +true+ if the field is a sequence
|
119
|
+
# # @return [Boolean] field is a sequence
|
120
|
+
# end
|
121
|
+
|
122
|
+
# Define a new field in the structure
|
123
|
+
# @param name [Symbol, String] the name of the field.
|
124
|
+
# @param type [Class, String, Proc] the type of the field, as a Class, or
|
125
|
+
# as a String or Proc that will be evaluated in the context of the
|
126
|
+
# {Structure} instance.
|
127
|
+
# @param length [Integer, String, Proc] if given, consider the field a
|
128
|
+
# vector of the type. The length is either a constant Integer of a
|
129
|
+
# String or Proc that will be evaluated in the context of the
|
130
|
+
# {Structure} instance.
|
131
|
+
# @param count [Integer, String, Proc] if given, consider the field is
|
132
|
+
# repeated count times. The count is either a constant Integer of a
|
133
|
+
# String or Proc that will be evaluated in the context of the
|
134
|
+
# {Structure} instance.
|
135
|
+
# @param offset [integer, String, Proc] if given, the absolute offset in
|
136
|
+
# the file, or the offset from the parent position, where the field can
|
137
|
+
# be found. See relative offset. The offset is either a constant
|
138
|
+
# Integer of a String or Proc that will be evaluated in the context
|
139
|
+
# of the {Structure} instance.
|
140
|
+
# @param sequence [Boolean] if true, +type+, +length+, +offset+, and
|
141
|
+
# +condition+ are evaluated for each repetition.
|
142
|
+
# @param condition [String, Proc] if given, the field, or repetition of the
|
143
|
+
# field can be conditionally present. The condition will be evaluated in
|
144
|
+
# the context of the {Structure} instance.
|
145
|
+
# @param relative_offset [Boolean] consider the +offset+ relative to the
|
146
|
+
# field +parent+.
|
147
|
+
# @param align [Boolean,Integer] if given, align the field. If given as an
|
148
|
+
# Integer it must be a power of 2. Else the field is aligned to the
|
149
|
+
# field's type preferred alignment.
|
150
|
+
# @param expect [Proc, Object] if given, the field value must validate or
|
151
|
+
# an eexception is raised. If a proc the proc resulted must be truthy. If
|
152
|
+
# not, the object will be tested for equality.
|
153
|
+
# @return self
|
154
|
+
def self.field(name, type, length: nil, count: nil, offset: nil,
|
155
|
+
sequence: false, condition: nil, relative_offset: false,
|
156
|
+
align: false, expect: nil)
|
157
|
+
if type.respond_to?(:always_align) && type.always_align
|
158
|
+
al = type.align
|
159
|
+
if align.kind_of?(Integer)
|
160
|
+
align = align >= al ? align : al
|
62
161
|
else
|
63
|
-
|
162
|
+
align = al
|
64
163
|
end
|
65
164
|
end
|
165
|
+
if align == true
|
166
|
+
# Automatic alignment not supported for dynamic types
|
167
|
+
if type.respond_to?(:align)
|
168
|
+
align = type.align
|
169
|
+
else
|
170
|
+
raise "alignment is unsupported for dynamic types"
|
171
|
+
end
|
172
|
+
else
|
173
|
+
raise "alignement must be a power of 2" if align && (align - 1) & align != 0
|
174
|
+
end
|
175
|
+
@fields.push(Field::new(name, type, length, count, offset, sequence,
|
176
|
+
condition, relative_offset, align, expect))
|
177
|
+
attr_accessor name
|
178
|
+
self
|
66
179
|
end
|
67
180
|
|
68
|
-
|
69
|
-
|
181
|
+
class << self
|
182
|
+
alias register_field field
|
70
183
|
end
|
71
184
|
|
72
|
-
|
73
|
-
|
185
|
+
# @!parse
|
186
|
+
# # @abstract Parent class for scalars
|
187
|
+
# class Scalar
|
188
|
+
# end
|
189
|
+
|
190
|
+
# @!macro attach
|
191
|
+
# @!parse
|
192
|
+
# # $4
|
193
|
+
# class $1 < Scalar
|
194
|
+
# # @method align
|
195
|
+
# # @scope class
|
196
|
+
# # Returns the alignment of {$1}.
|
197
|
+
# # @return [Integer] the byte boundary to align the type to.
|
198
|
+
# # @method shape(value = nil, offset = 0, parent = nil, index = nil, kind = DataShape, length = nil)
|
199
|
+
# # @scope class
|
200
|
+
# # Returns the shape of a field of type {$1}
|
201
|
+
# # @param value [Object] ignored.
|
202
|
+
# # @param offset [Integer] start of the shape.
|
203
|
+
# # @param parent [Structure] ignored.
|
204
|
+
# # @param index [Integer] ignored.
|
205
|
+
# # @param kind [Class] shape class. Will be instantiated through
|
206
|
+
# # new with the +offset+ and <tt>offset + sizeof($3) * length - 1</tt>.
|
207
|
+
# # @param length [Integer] if given the length of the vector. Else
|
208
|
+
# # the length is considered to be 1.
|
209
|
+
# # @return [kind] a new instance of +kind+
|
210
|
+
# # @method size(value = nil, offset = 0, parent = nil, index = nil, length = nil)
|
211
|
+
# # @scope class
|
212
|
+
# # Returns the size of a field of type {$1}.
|
213
|
+
# # @param value [Object] ignored.
|
214
|
+
# # @param offset [Integer] ignored.
|
215
|
+
# # @param parent [Structure] ignored.
|
216
|
+
# # @param index [Integer] ignored.
|
217
|
+
# # @param length [Integer] if given the length of the vector. Else
|
218
|
+
# # the length is considered to be 1.
|
219
|
+
# # @return [Integer] the type <tt>sizeof($3) * length</tt>.
|
220
|
+
# # @method load(input, input_big = LibBin::default_big?, parent = nil, index = nil, length = nil)
|
221
|
+
# # @scope class
|
222
|
+
# # Load a field of type {$1} from +input+, and return it.
|
223
|
+
# # @param input [IO] the stream to load the field from.
|
224
|
+
# # @param input_big [Boolean] the endianness of +input+
|
225
|
+
# # @param parent [Structure] ignored.
|
226
|
+
# # @param index [Integer] ignored.
|
227
|
+
# # @param length [Integer] if given the length of the vector. Else
|
228
|
+
# # the length is considered to be 1.
|
229
|
+
# # @return [Numeric, Array<Numeric>] the Ruby representation of the
|
230
|
+
# # type, or the Array representation of the vector if +length+
|
231
|
+
# # was specified
|
232
|
+
# # @method dump(value, output, output_big = LibBin::default_big?, parent = nil, index = nil, length = nil)
|
233
|
+
# # @scope class
|
234
|
+
# # Dump a field of class {$1} to +output+.
|
235
|
+
# # @param value [Numeric, Array<Numeric>] the Ruby representation
|
236
|
+
# # of the type, or the Array representation of the vector if
|
237
|
+
# # +length+ is specified.
|
238
|
+
# # @param output [IO] the stream to dump the field to.
|
239
|
+
# # @param output_big [Boolean] the endianness of +output+.
|
240
|
+
# # @param parent [Structure] ignored.
|
241
|
+
# # @param index [Integer] ignored.
|
242
|
+
# # @param length [Integer] if given the length of the vector. Else
|
243
|
+
# # the length is considered to be 1.
|
244
|
+
# # @return [nil]
|
245
|
+
# # @method convert(input, output, input_big = LibBin::default_big?, output_big = !input_big, parent = nil, index = nil, length = nil)
|
246
|
+
# # @scope class
|
247
|
+
# # Convert a field of class {$1} by loading it from +input+ and
|
248
|
+
# # dumping it to +output+. Returns the loaded field.
|
249
|
+
# # @param input [IO] the stream to load the field from.
|
250
|
+
# # @param output [IO] the stream to dump the field to.
|
251
|
+
# # @param input_big [Boolean] the endianness of +input+
|
252
|
+
# # @param output_big [Boolean] the endianness of +output+.
|
253
|
+
# # @param parent [Structure] ignored.
|
254
|
+
# # @param index [Integer] ignored.
|
255
|
+
# # @param length [Integer] if given the length of the vector. Else
|
256
|
+
# # the length is considered to be 1.
|
257
|
+
# # @return [Numeric, Array<Numeric>] the Ruby representation of the
|
258
|
+
# # type, or the Array representation of the vector if +length+
|
259
|
+
# # was specified
|
260
|
+
# end
|
261
|
+
# @!method $2 method_signature(name, length: nil, count: nil, offset: nil, sequence: false, condition: nil, relative_offset: false, align: false)
|
262
|
+
# @!scope class
|
263
|
+
# Create a new field of type {$1} and name +name+. See {field} for options.
|
264
|
+
# @return self
|
265
|
+
#
|
266
|
+
def self.define_scalar_constructor(klassname, name, mapped_type, description)
|
267
|
+
eval <<EOF
|
268
|
+
def self.#{name}(name, length: nil, count: nil, offset: nil, sequence: false, condition: nil, relative_offset: false, align: false, expect: nil)
|
269
|
+
if align == true
|
270
|
+
align = #{klassname}.align
|
271
|
+
else
|
272
|
+
raise "alignement must be a power of 2" if align && (align - 1) & align != 0
|
273
|
+
end
|
274
|
+
@fields.push(Field::new(name, #{klassname}, length, count, offset, sequence, condition, relative_offset, align, expect))
|
275
|
+
attr_accessor name
|
276
|
+
self
|
277
|
+
end
|
278
|
+
EOF
|
74
279
|
end
|
280
|
+
private_class_method :define_scalar_constructor
|
75
281
|
|
76
|
-
|
77
|
-
|
282
|
+
# @!group Scalars
|
283
|
+
define_scalar_constructor "Int8", :int8, :int8_t, "A signed 8 bit integer"
|
284
|
+
define_scalar_constructor "UInt8", :uint8, :uint8_t, "An unsigned 8 bit integer"
|
285
|
+
define_scalar_constructor "Int16", :int16, :int16_t, "A signed 16 bit integer"
|
286
|
+
define_scalar_constructor "Int16_LE", :int16_le, :int16_t, "A signed little endian 16 bit integer"
|
287
|
+
define_scalar_constructor "Int16_BE", :int16_be, :int16_t, "A signed big endian 16 bit integer"
|
288
|
+
define_scalar_constructor "UInt16", :uint16, :uint16_t, "An unsigned 16 bit integer"
|
289
|
+
define_scalar_constructor "UInt16_LE", :uint16_le, :uint16_t, "An unsigned little endian 16 bit integer"
|
290
|
+
define_scalar_constructor "UInt16_BE", :uint16_be, :uint16_t, "An unsigned big endian 16 bit integer"
|
291
|
+
define_scalar_constructor "Int32", :int32, :int32_t, "A signed little endian 32 bit integer"
|
292
|
+
define_scalar_constructor "Int32_LE", :int32_le, :int32_t, "A signed little endian 32 bit integer"
|
293
|
+
define_scalar_constructor "Int32_BE", :int32_be, :int32_t, "A signed big endian 32 bit integer"
|
294
|
+
define_scalar_constructor "UInt32", :uint32, :uint32_t, "An unsigned 32 bit integer"
|
295
|
+
define_scalar_constructor "UInt32_LE", :uint32_le, :uint32_t, "An unsigned little endian 32 bit integer"
|
296
|
+
define_scalar_constructor "UInt32_BE", :uint32_be, :uint32_t, "An unsigned big endian 32 bit integer"
|
297
|
+
define_scalar_constructor "Int64", :int64, :int64_t, "A signed little endian 64 bit integer"
|
298
|
+
define_scalar_constructor "Int64_LE", :int64_le, :int64_t, "A signed little endian 64 bit integer"
|
299
|
+
define_scalar_constructor "Int64_BE", :int64_be, :int64_t, "A signed big endian 64 bit integer"
|
300
|
+
define_scalar_constructor "UInt64", :uint64, :uint64_t, "An unsigned 64 bit integer"
|
301
|
+
define_scalar_constructor "UInt64_LE", :uint64_le, :uint64_t, "An unsigned little endian 64 bit integer"
|
302
|
+
define_scalar_constructor "UInt64_BE", :uint64_be, :uint64_t, "An unsigned big endian 64 bit integer"
|
303
|
+
define_scalar_constructor "Flt", :float, :float, "A single precision floating point scalar"
|
304
|
+
define_scalar_constructor "Flt_LE", :float_le, :float, "A single precision little endian floating point scalar"
|
305
|
+
define_scalar_constructor "Flt_BE", :float_be, :float, "A single precision big endian floating point scalar"
|
306
|
+
define_scalar_constructor "Double", :double, :double, "A double precision floating point scalar"
|
307
|
+
define_scalar_constructor "Double_LE", :double_le, :double, "A double precision little endian floating point scalar"
|
308
|
+
define_scalar_constructor "Double_BE", :double_be, :double, "A double precision big endian floating point scalar"
|
309
|
+
define_scalar_constructor "Half", :half, :half, "A half precision floating point scalar"
|
310
|
+
define_scalar_constructor "Half_LE", :half_le, :half, "A half precision little endian floating point scalar"
|
311
|
+
define_scalar_constructor "Half_BE", :half_be, :half, "A half precision big endian floating point scalar"
|
312
|
+
define_scalar_constructor "PGHalf", :pghalf, :pghalf, "A half precision floating point scalar as used by PlatinumGames in certain formats"
|
313
|
+
define_scalar_constructor "PGHalf_LE", :pghalf_le, :pghalf, "A half precision little endian floating point scalar as used by PlatinumGames in certain formats"
|
314
|
+
define_scalar_constructor "PGHalf_BE", :pghalf_be, :pghalf, "A half precision big endian floating point scalar as used by PlatinumGames in certain formats"
|
315
|
+
|
316
|
+
# @!parse
|
317
|
+
# # A string type, can be NULL terminated or have an arbitrary length.
|
318
|
+
# class Str < Scalar
|
319
|
+
# end
|
320
|
+
|
321
|
+
# Create a new field of type {Str} and name +name+. See {field} for options.
|
322
|
+
# @return self
|
323
|
+
def self.string(field, length = nil, count: nil, offset: nil, sequence: false, condition: nil, relative_offset: false, align: false, expect: nil)
|
324
|
+
if align == true
|
325
|
+
align = Str.align
|
326
|
+
else
|
327
|
+
raise "alignement must be a power of 2" if align && (align - 1) & align != 0
|
328
|
+
end
|
329
|
+
@fields.push(Field::new(field, Str, length, count, offset, sequence, condition, relative_offset, align, expect))
|
330
|
+
attr_accessor field
|
331
|
+
self
|
78
332
|
end
|
79
333
|
|
80
|
-
|
334
|
+
# @abstract A parent class representing an enumeration that is loading integers as symbols.
|
335
|
+
# Useer should inherit this base class.
|
336
|
+
class Enum
|
337
|
+
class << self
|
338
|
+
#get the enum map
|
339
|
+
attr_reader :map
|
340
|
+
# Get the underlying type
|
341
|
+
attr_accessor :type
|
342
|
+
|
343
|
+
# Specify the size of the underlying type
|
344
|
+
def type_size=(sz)
|
345
|
+
t = eval "Int#{sz}"
|
346
|
+
raise "unsupported enum size #{sz}" unless t
|
347
|
+
@type = t
|
348
|
+
return sz
|
349
|
+
end
|
350
|
+
alias set_type_size type_size=
|
351
|
+
|
352
|
+
# Get the underlying type size in bits
|
353
|
+
def type_size
|
354
|
+
@type.size
|
355
|
+
end
|
356
|
+
|
357
|
+
# Set the enum map
|
358
|
+
# @param m [Hash{Symbol=>Integer}] enum values
|
359
|
+
def map=(m)
|
360
|
+
@map_to = m.invert
|
361
|
+
@map_from = @map = m
|
362
|
+
end
|
363
|
+
alias set_map map=
|
364
|
+
|
365
|
+
# @!visibility private
|
366
|
+
def inherited(subclass)
|
367
|
+
subclass.instance_variable_set(:@type, Int32)
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
# Returns the underlying type +always_align+ property
|
372
|
+
def self.always_align
|
373
|
+
@type.always_align
|
374
|
+
end
|
375
|
+
|
376
|
+
# Returns the underlying type +align+ property
|
377
|
+
def self.align
|
378
|
+
@type.align
|
379
|
+
end
|
81
380
|
|
82
|
-
#
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
#
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
#
|
93
|
-
#
|
94
|
-
#
|
95
|
-
#
|
96
|
-
#
|
97
|
-
#
|
98
|
-
#
|
99
|
-
#
|
100
|
-
#
|
101
|
-
#
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
# end
|
111
|
-
|
112
|
-
class DataConverter
|
113
|
-
|
114
|
-
SCALAR_TYPES = {
|
115
|
-
:c => [:Int8, :int8],
|
116
|
-
:C => [:UInt8, :uint8],
|
117
|
-
:s => [:Int16, :int16],
|
118
|
-
:"s<" => [:Int16_LE, :int16_le],
|
119
|
-
:"s>" => [:Int16_BE, :int16_be],
|
120
|
-
:S => [:UInt16, :uint16],
|
121
|
-
:"S<" => [:UInt16_LE, :uint16_le],
|
122
|
-
:"S>" => [:UInt16_BE, :uint16_be],
|
123
|
-
:v => [:UInt16_LE, :uint16_le],
|
124
|
-
:n => [:UInt16_BE, :uint16_be],
|
125
|
-
:l => [:Int32, :int32],
|
126
|
-
:"l<" => [:Int32_LE, :int32_le],
|
127
|
-
:"l>" => [:Int32_BE, :int32_be],
|
128
|
-
:L => [:UInt32, :uint32],
|
129
|
-
:"L<" => [:UInt32_LE, :uint32_le],
|
130
|
-
:"L>" => [:UInt32_BE, :uint32_be],
|
131
|
-
:V => [:UInt32_LE, :uint32_le],
|
132
|
-
:N => [:UInt32_BE, :uint32_be],
|
133
|
-
:q => [:Int64, :int64],
|
134
|
-
:"q<" => [:Int64_LE, :int64_le],
|
135
|
-
:"q>" => [:Int64_BE, :int64_be],
|
136
|
-
:Q => [:UInt64, :uint64],
|
137
|
-
:"Q<" => [:UInt64_LE, :uint64_le],
|
138
|
-
:"Q>" => [:UInt64_BE, :uint64_be],
|
139
|
-
:F => [:Flt, :float],
|
140
|
-
:e => [:Flt_LE, :float_le],
|
141
|
-
:g => [:Flt_BE, :float_be],
|
142
|
-
:D => [:Double, :double],
|
143
|
-
:E => [:Double_LE, :double_le],
|
144
|
-
:G => [:Double_BE, :double_be],
|
145
|
-
:half => [:Half, :half],
|
146
|
-
:half_le => [:Half_LE, :half_le],
|
147
|
-
:half_be => [:Half_BE, :half_be],
|
148
|
-
:pghalf => [:PGHalf, :pghalf],
|
149
|
-
:pghalf_le => [:PGHalf_LE, :pghalf_le],
|
150
|
-
:pghalf_be => [:PGHalf_BE, :pghalf_be]
|
151
|
-
}
|
152
|
-
|
153
|
-
def self.register_field(field, type, length: nil, count: nil, offset: nil, sequence: false, condition: nil, relative_offset: false)
|
154
|
-
if type.kind_of?(Symbol)
|
155
|
-
if type[0] == 'a'
|
156
|
-
real_type = Str
|
381
|
+
# Forwards the call to the underlying type
|
382
|
+
def self.size(value = nil, previous_offset = 0, parent = nil, index = nil, length = nil)
|
383
|
+
@type.size(value, previous_offset, parent, index, length)
|
384
|
+
end
|
385
|
+
|
386
|
+
# Forwards the call to the underlying type
|
387
|
+
def self.shape(value = nil, previous_offset = 0, parent = nil, index = nil, kind = DataShape, length = nil)
|
388
|
+
@type.shape(value, previous_offset, parent, index, kind, length)
|
389
|
+
end
|
390
|
+
|
391
|
+
# Load a field of type {Enum} from +input+, and return it.
|
392
|
+
# @param input [IO] the stream to load the field from.
|
393
|
+
# @param input_big [Boolean] the endianness of +input+
|
394
|
+
# @param parent [Structure] ignored.
|
395
|
+
# @param index [Integer] ignored.
|
396
|
+
# @param length [Integer] if given the length of the vector. Else
|
397
|
+
# the length is considered to be 1.
|
398
|
+
# @return [Symbol,Integer,Array<Symbol,Integer>] the Ruby
|
399
|
+
# representation of the type, or the Array representation of the
|
400
|
+
# vector if +length+ was specified
|
401
|
+
def self.load(input, input_big = LibBin::default_big?, parent = nil, index = nil, length = nil)
|
402
|
+
v = @type.load(input, input_big, parent, index, length)
|
403
|
+
if length
|
404
|
+
v.collect { |val|
|
405
|
+
n = @map_to[val]
|
406
|
+
n = val unless n
|
407
|
+
n
|
408
|
+
}
|
157
409
|
else
|
158
|
-
|
410
|
+
n = @map_to[v]
|
411
|
+
n = v unless n
|
412
|
+
n
|
159
413
|
end
|
160
|
-
else
|
161
|
-
real_type = type
|
162
414
|
end
|
163
|
-
|
164
|
-
|
415
|
+
|
416
|
+
# Convert a field of class {Enum} by loading it from +input+ and
|
417
|
+
# dumping it to +output+. Returns the loaded field.
|
418
|
+
# @param input [IO] the stream to load the field from.
|
419
|
+
# @param output [IO] the stream to dump the field to.
|
420
|
+
# @param input_big [Boolean] the endianness of +input+
|
421
|
+
# @param output_big [Boolean] the endianness of +output+.
|
422
|
+
# @param parent [Structure] ignored.
|
423
|
+
# @param index [Integer] ignored.
|
424
|
+
# @param length [Integer] if given the length of the vector. Else
|
425
|
+
# the length is considered to be 1.
|
426
|
+
# @return [Symbol,Integer,Array<Symbol,Integer>] the Ruby representation
|
427
|
+
# of the type, or the Array representation of the vector if +length+
|
428
|
+
# was specified
|
429
|
+
def self.convert(input, output, input_big = LibBin::default_big?, output_big = !input_big, parent = nil, index = nil, length = nil)
|
430
|
+
v = @type.convert(input, output, input_big, output_big, parent, index, length)
|
431
|
+
if length
|
432
|
+
v.collect { |val|
|
433
|
+
n = @map_to[val]
|
434
|
+
n = val unless n
|
435
|
+
n
|
436
|
+
}
|
437
|
+
else
|
438
|
+
n = @map_to[v]
|
439
|
+
n = v unless n
|
440
|
+
n
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
# Dump a field of class {Enum} to +output+.
|
445
|
+
# @param value [Numeric, Array<Numeric>] the Ruby representation
|
446
|
+
# of the type, or the Array representation of the vector if
|
447
|
+
# +length+ is specified.
|
448
|
+
# @param output [IO] the stream to dump the field to.
|
449
|
+
# @param output_big [Boolean] the endianness of +output+.
|
450
|
+
# @param parent [Structure] ignored.
|
451
|
+
# @param index [Integer] ignored.
|
452
|
+
# @param length [Integer] if given the length of the vector. Else
|
453
|
+
# the length is considered to be 1.
|
454
|
+
# @return [nil]
|
455
|
+
def self.dump(value, output, output_big = LibBin::default_big?, parent = nil, index = nil, length = nil)
|
456
|
+
if length
|
457
|
+
v = length.times.collect { |i|
|
458
|
+
val = @map_from[value[i]]
|
459
|
+
val = value[i] unless val
|
460
|
+
val
|
461
|
+
}
|
462
|
+
else
|
463
|
+
v = @map_from[value]
|
464
|
+
v = value unless v
|
465
|
+
end
|
466
|
+
@type.dump(v, output, output_big, parent, index, length)
|
467
|
+
end
|
165
468
|
end
|
166
469
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
470
|
+
# Create a new field of a type inheriting from {Enum} and name +name+.
|
471
|
+
# See {field} for more options
|
472
|
+
# @param name [Symbol,String] name of the field.
|
473
|
+
# @param map [Hash{Symbol => Integer}] enum values.
|
474
|
+
# @param size [Integer] size in bits of the underlying integer
|
475
|
+
# @return self
|
476
|
+
def self.enum(name, map, size: 32, length: nil, count: nil, offset: nil, sequence: false, condition: nil, relative_offset: false, align: false, expect: nil)
|
477
|
+
klass = Class.new(Enum) do |c|
|
478
|
+
c.type_size = size
|
479
|
+
c.map = map
|
480
|
+
end
|
481
|
+
if klass.always_align
|
482
|
+
al = klass.align
|
483
|
+
if align.kind_of?(Integer)
|
484
|
+
align = align >= al ? align : al
|
485
|
+
else
|
486
|
+
align = al
|
487
|
+
end
|
488
|
+
end
|
489
|
+
if align == true
|
490
|
+
align = klass.align
|
491
|
+
else
|
492
|
+
raise "alignement must be a power of 2" if align && (align - 1) & align != 0
|
493
|
+
end
|
494
|
+
@fields.push(Field::new(name, klass, length, count, offset, sequence, condition, relative_offset, align, expect))
|
495
|
+
attr_accessor name
|
496
|
+
self
|
173
497
|
end
|
174
|
-
|
498
|
+
|
499
|
+
# @abstract A parent class that represent a bitfield that is
|
500
|
+
# loading integers as an instance of itself.
|
501
|
+
# User should inherit this base class.
|
502
|
+
class Bitfield
|
503
|
+
class << self
|
504
|
+
# Bitfield's field names and number of bits
|
505
|
+
attr_reader :map
|
506
|
+
# Signedness of the underlying type
|
507
|
+
attr_reader :signed
|
508
|
+
# Underlying type
|
509
|
+
attr_reader :type
|
510
|
+
|
511
|
+
# Set the size and signedness of the underlying type
|
512
|
+
def set_type_size(sz, signed = false)
|
513
|
+
tname = "#{signed ? "" : "U"}Int#{sz}"
|
514
|
+
t = eval tname
|
515
|
+
raise "unsupported bitfield type #{tname}" unless t
|
516
|
+
@type = t
|
517
|
+
@signed = signed
|
518
|
+
return sz
|
519
|
+
end
|
520
|
+
|
521
|
+
# Set the underlying type
|
522
|
+
def type=(type)
|
523
|
+
@type = type
|
524
|
+
@signed = type.name.split('::').last[0] != "U"
|
525
|
+
type
|
526
|
+
end
|
527
|
+
alias set_type type=
|
528
|
+
|
529
|
+
# Set the bitfield's field names and number of bits
|
530
|
+
# Creates accessor to fields for instance of this class
|
531
|
+
def map=(m)
|
532
|
+
raise "map already set" if @map
|
533
|
+
@map = m.each.collect { |k, v| [k, [v, k.to_sym, :"#{k}="]] }.to_h
|
534
|
+
m.each_key { |k|
|
535
|
+
attr_accessor k
|
536
|
+
}
|
537
|
+
m
|
538
|
+
end
|
539
|
+
alias set_map map=
|
540
|
+
|
541
|
+
# @!visibility private
|
542
|
+
def inherited(subclass)
|
543
|
+
subclass.instance_variable_set(:@type, UInt32)
|
544
|
+
end
|
545
|
+
end
|
546
|
+
|
547
|
+
# Unused bits of underlying value
|
548
|
+
attr_accessor :__remainder
|
549
|
+
|
550
|
+
# set only the unused bits of the underlying value
|
551
|
+
def __remainder=(v) # only keep relevant bits of the remainder
|
552
|
+
if v != 0
|
553
|
+
num_bits = self.class.type.size * 8
|
554
|
+
num_used_bits = self.class.map.value.collect { |v, _, _| v }.select { |v| v > 0 }.sum(:+)
|
555
|
+
if num_used_bits < num_bits
|
556
|
+
v &= ((( 1 << (num_bits - num_used_bits)) - 1) << num_used_bits)
|
557
|
+
else
|
558
|
+
v = 0
|
559
|
+
end
|
560
|
+
end
|
561
|
+
@__remainder = v
|
562
|
+
end
|
563
|
+
|
564
|
+
# @!visibility private
|
565
|
+
def initialize
|
566
|
+
@__remainder = 0
|
567
|
+
end
|
568
|
+
|
569
|
+
# @!visibility private
|
570
|
+
def __set_value(val)
|
571
|
+
base = 0
|
572
|
+
self.class.map.each { |k, (v, _, s)|
|
573
|
+
next if v <= 0
|
574
|
+
tmp = (val >> base) & ((1 << v) - 1)
|
575
|
+
val ^= tmp << base #remove bits from val
|
576
|
+
tmp -= (1 << v) if self.class.signed && tmp >= (1 << (v - 1))
|
577
|
+
send(s, tmp)
|
578
|
+
base += v
|
579
|
+
}
|
580
|
+
@__remainder = val
|
581
|
+
end
|
582
|
+
|
583
|
+
# @!visibility private
|
584
|
+
def __get_value
|
585
|
+
base = 0
|
586
|
+
val = 0
|
587
|
+
self.class.map.each { |k, (v, g, _)|
|
588
|
+
next if v <= 0
|
589
|
+
val |= ((send(g) & ((1 << v) - 1)) << base)
|
590
|
+
base += v
|
591
|
+
}
|
592
|
+
val | @__remainder
|
593
|
+
end
|
594
|
+
|
595
|
+
# Returns the underlying type +always_align+ property
|
596
|
+
def self.always_align
|
597
|
+
@type.always_align
|
598
|
+
end
|
599
|
+
|
600
|
+
# Returns the underlying type +align+ property
|
601
|
+
def self.align
|
602
|
+
@type.align
|
603
|
+
end
|
604
|
+
|
605
|
+
# Forwards the call to the underlying type
|
606
|
+
def self.size(value = nil, previous_offset = 0, parent = nil, index = nil, length = nil)
|
607
|
+
@type.size(value, previous_offset, parent, index, length)
|
608
|
+
end
|
609
|
+
|
610
|
+
# Forwards the call to the underlying type
|
611
|
+
def self.shape(value = nil, previous_offset = 0, parent = nil, index = nil, kind = DataShape, length = nil)
|
612
|
+
@type.shape(value, previous_offset, parent, index, kind, length)
|
613
|
+
end
|
614
|
+
|
615
|
+
# Load a {Bitfield} from +input+, and return it.
|
616
|
+
# @param input [IO] the stream to load the field from.
|
617
|
+
# @param input_big [Boolean] the endianness of +input+
|
618
|
+
# @param parent [Structure] ignored.
|
619
|
+
# @param index [Integer] ignored.
|
620
|
+
# @param length [Integer] if given the length of the vector. Else
|
621
|
+
# the length is considered to be 1.
|
622
|
+
# @return [Bitfield,Array<Bitfield>] an instance of self, or an
|
623
|
+
# Array if length was specified
|
624
|
+
def self.load(input, input_big = LibBin::default_big?, parent = nil, index = nil, length = nil)
|
625
|
+
v = @type.load(input, input_big, parent, index, length)
|
626
|
+
if length
|
627
|
+
v.collect { |val|
|
628
|
+
bf = self.new
|
629
|
+
bf.__set_value(val)
|
630
|
+
bf
|
631
|
+
}
|
632
|
+
else
|
633
|
+
bf = self.new
|
634
|
+
bf.__set_value(v)
|
635
|
+
bf
|
636
|
+
end
|
637
|
+
end
|
638
|
+
|
639
|
+
# Convert a {Bitfield} by loading it from +input+ and
|
640
|
+
# dumping it to +output+. Returns the loaded field.
|
641
|
+
# @param input [IO] the stream to load the field from.
|
642
|
+
# @param output [IO] the stream to dump the field to.
|
643
|
+
# @param input_big [Boolean] the endianness of +input+
|
644
|
+
# @param output_big [Boolean] the endianness of +output+.
|
645
|
+
# @param parent [Structure] ignored.
|
646
|
+
# @param index [Integer] ignored.
|
647
|
+
# @param length [Integer] if given the length of the vector. Else
|
648
|
+
# the length is considered to be 1.
|
649
|
+
# @return [Bitfield,Array<Bitfield>] an instance of self, or an
|
650
|
+
# Array if length was specified
|
651
|
+
def self.convert(input, output, input_big = LibBin::default_big?, output_big = !input_big, parent = nil, index = nil, length = nil)
|
652
|
+
v = @type.convert(input, output, input_big, output_big, parent, index, length)
|
653
|
+
if length
|
654
|
+
v.collect { |val|
|
655
|
+
bf = self.new
|
656
|
+
bf.__set_value(val)
|
657
|
+
bf
|
658
|
+
}
|
659
|
+
else
|
660
|
+
bf = self.new
|
661
|
+
bf.__set_value(v)
|
662
|
+
bf
|
663
|
+
end
|
664
|
+
end
|
665
|
+
|
666
|
+
# Dump a {Bitfield} to +output+.
|
667
|
+
# @param value [Bitfield, Array<Bitfield>] the Ruby representation
|
668
|
+
# of the type, or the Array representation of the vector if
|
669
|
+
# +length+ is specified.
|
670
|
+
# @param output [IO] the stream to dump the field to.
|
671
|
+
# @param output_big [Boolean] the endianness of +output+.
|
672
|
+
# @param parent [Structure] ignored.
|
673
|
+
# @param index [Integer] ignored.
|
674
|
+
# @param length [Integer] if given the length of the vector. Else
|
675
|
+
# the length is considered to be 1.
|
676
|
+
# @return [nil]
|
677
|
+
def self.dump(value, output, output_big = LibBin::default_big?, parent = nil, index = nil, length = nil)
|
678
|
+
v =
|
679
|
+
if length
|
680
|
+
length.times.collect { |i|
|
681
|
+
value[i].__get_value
|
682
|
+
}
|
683
|
+
else
|
684
|
+
value.__get_value
|
685
|
+
end
|
686
|
+
@type.dump(v, output, output_big, parent, index, length)
|
687
|
+
end
|
175
688
|
end
|
176
689
|
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
690
|
+
# Create a new field of a type inheriting from {Bitfield} and name +name+.
|
691
|
+
# See {field} for more options
|
692
|
+
# @param name [Symbol,String] name of the field.
|
693
|
+
# @param map [Hash{Symbol => Integer}] bitfield field names and number of bits.
|
694
|
+
# @param size [Integer] size in bits of the underlying integer
|
695
|
+
# @param signed [Boolean] signedness of the underlying type
|
696
|
+
# @return self
|
697
|
+
def self.bitfield(name, map, size: 32, signed: false, length: nil, count: nil, offset: nil, sequence: false, condition: nil, relative_offset: false, align: false, expect: nil)
|
698
|
+
klass = Class.new(Bitfield) do |c|
|
699
|
+
c.set_type_size(size, signed)
|
700
|
+
c.set_map(map)
|
701
|
+
end
|
702
|
+
if klass.always_align
|
703
|
+
al = klass.align
|
704
|
+
if align.kind_of?(Integer)
|
705
|
+
align = align >= al ? align : al
|
706
|
+
else
|
707
|
+
align = al
|
708
|
+
end
|
709
|
+
end
|
710
|
+
if align == true
|
711
|
+
align = klass.align
|
712
|
+
else
|
713
|
+
raise "alignement must be a power of 2" if align && (align - 1) & align != 0
|
714
|
+
end
|
715
|
+
@fields.push(Field::new(name, klass, length, count, offset, sequence, condition, relative_offset, align, expect))
|
716
|
+
attr_accessor name
|
717
|
+
self
|
195
718
|
end
|
196
719
|
|
197
720
|
end
|