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.
@@ -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
- class DataShape
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
- @members = args[0]
30
- if @members.kind_of?(Hash)
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 = @members.compact.collect(&:range).reduce(:+)
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 DataRange
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 :range
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 == 2
58
- @range = Range::new(args[0], args[1])
59
- else
60
- if args[0].kind_of?(Hash)
61
- @range = args[0].values.compact.collect(&:range).reduce(:+)
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
- @range = args[0].compact.collect(&:range).reduce(:+)
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
- def first
69
- @range.first
181
+ class << self
182
+ alias register_field field
70
183
  end
71
184
 
72
- def last
73
- @range.last
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
- def size
77
- @range.size
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
- end
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
- # class Field
83
- # attr_reader :name,
84
- # :type,
85
- # :length,
86
- # :count,
87
- # :offset,
88
- # :sequence,
89
- # :condition
90
- #
91
- # def sequence?
92
- # @sequence
93
- # end
94
- #
95
- # def relative_offset?
96
- # @relative_offset
97
- # end
98
- #
99
- # def initialize(name, type, length, count, offset, sequence, condition, relative_offset)
100
- # @name = name
101
- # @type = type
102
- # @length = length
103
- # @count = count
104
- # @offset = offset
105
- # @sequence = sequence
106
- # @condition = condition
107
- # @relative_offset = relative_offset
108
- # end
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
- real_type = const_get(SCALAR_TYPES[type][0])
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
- @fields.push(Field::new(field, real_type, length, count, offset, sequence, condition, relative_offset))
164
- attr_accessor field
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
- def self.create_scalar_accessor(symbol)
168
- klassname, name = SCALAR_TYPES[symbol]
169
- eval <<EOF
170
- def self.#{name}(field, length: nil, count: nil, offset: nil, sequence: false, condition: nil, relative_offset: false)
171
- @fields.push(Field::new(field, #{klassname}, length, count, offset, sequence, condition, relative_offset))
172
- attr_accessor field
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
- EOF
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
- [:c,
178
- :C,
179
- :s, :"s<", :"s>",
180
- :S, :"S<", :"S>",
181
- :l, :"l<", :"l>",
182
- :L, :"L<", :"L>",
183
- :q, :"q<", :"q>",
184
- :Q, :"Q<", :"Q>",
185
- :F, :e, :g,
186
- :D, :E, :G,
187
- :half, :half_le, :half_be,
188
- :pghalf, :pghalf_le, :pghalf_be].each { |c|
189
- create_scalar_accessor(c)
190
- }
191
-
192
- def self.string( field, length = nil, count: nil, offset: nil, sequence: false, condition: nil, relative_offset: false)
193
- @fields.push(Field::new(field, Str, length, count, offset, sequence, condition, relative_offset))
194
- attr_accessor field
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