libbin 1.0.8 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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