libbin 1.0.5 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,489 +1,720 @@
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
- Range::new(first <= other.first ? first : other.first,
7
- last >= other.last ? last : other.last,
8
- exclude_end?)
9
+ return other.dup unless min
10
+ return self.dup unless other.min
11
+ Range::new(min <= other.min ? min : other.min,
12
+ max >= other.max ? max : other.max)
9
13
  end
10
14
  end
11
15
  end
12
16
 
13
- class DataShape
17
+ # Classs that can be used to get the shape of a {Structure} or of a {Structure::Scalar}.
18
+ class DataRange
14
19
  using RangeRefinement
15
20
  attr_reader :range
16
- attr_reader :members
17
-
18
- def method_missing(m, *arg, &block)
19
- return @members[m] if @members && @members[m]
20
- super
21
- end
22
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
23
32
  def initialize(*args)
24
33
  if args.length == 2
25
34
  @range = Range::new(args[0], args[1])
26
- @members = nil
27
35
  else
28
- @members = args[0]
29
- @range = @members.values.flatten.compact.collect(&:range).reduce { |memo, obj| memo + obj }
36
+ if args[0].kind_of?(Hash)
37
+ @range = args[0].values.compact.collect(&:range).reduce(:+)
38
+ else
39
+ @range = args[0].compact.collect(&:range).reduce(:+)
40
+ end
30
41
  end
31
42
  end
32
43
 
44
+ # Return the beginning of the shape
45
+ # @return [Integer]
33
46
  def first
34
47
  @range.first
35
48
  end
36
49
 
50
+ # Return the end of the shape
51
+ # @return [Integer]
37
52
  def last
38
53
  @range.last
39
54
  end
40
55
 
56
+ # Return the size of the shape
57
+ # @return [Integer]
41
58
  def size
42
59
  @range.size
43
60
  end
44
61
 
45
62
  end
46
63
 
47
- 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
48
67
  using RangeRefinement
49
- attr_reader :range
68
+ attr_reader :members
50
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
51
87
  def initialize(*args)
52
- if args.length == 2
53
- @range = Range::new(args[0], args[1])
54
- else
55
- @range = args[0].values.flatten.compact.collect(&:range).reduce { |memo, obj| memo + obj }
88
+ if args.length == 1
89
+ @members = args[0]
56
90
  end
91
+ super
57
92
  end
58
93
 
59
- def first
60
- @range.first
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
161
+ else
162
+ align = al
163
+ end
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
61
179
  end
62
180
 
63
- def last
64
- @range.last
181
+ class << self
182
+ alias register_field field
65
183
  end
66
184
 
67
- def size
68
- @range.size
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
279
+ end
280
+ private_class_method :define_scalar_constructor
281
+
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
69
332
  end
70
333
 
71
- 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=
72
351
 
73
- class Field
74
- attr_reader :name,
75
- :type,
76
- :length,
77
- :count,
78
- :offset,
79
- :sequence,
80
- :condition
81
-
82
- def sequence?
83
- @sequence
84
- end
352
+ # Get the underlying type size in bits
353
+ def type_size
354
+ @type.size
355
+ end
85
356
 
86
- def relative_offset?
87
- @relative_offset
88
- end
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=
89
364
 
90
- def initialize(name, type, length, count, offset, sequence, condition, relative_offset)
91
- @name = name
92
- @type = type
93
- @length = length
94
- @count = count
95
- @offset = offset
96
- @sequence = sequence
97
- @condition = condition
98
- @relative_offset = relative_offset
99
- end
365
+ # @!visibility private
366
+ def inherited(subclass)
367
+ subclass.instance_variable_set(:@type, Int32)
368
+ end
369
+ end
100
370
 
101
- end
371
+ # Returns the underlying type +always_align+ property
372
+ def self.always_align
373
+ @type.always_align
374
+ end
102
375
 
103
- class DataConverter
376
+ # Returns the underlying type +align+ property
377
+ def self.align
378
+ @type.align
379
+ end
104
380
 
105
- rl = lambda { |type, str, number = nil|
106
- if number
107
- str.unpack(type.to_s+number.to_s)
108
- else
109
- str.unpack(type.to_s).first
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)
110
384
  end
111
- }
112
385
 
113
- sl = lambda { |type, value, number = nil|
114
- if number
115
- value.pack(type.to_s+number.to_s)
116
- else
117
- [value].pack(type.to_s)
118
- end
119
- }
120
-
121
- l = lambda { |type|
122
- [rl.curry[type], sl.curry[type]]
123
- }
124
-
125
- SCALAR_TYPES = {
126
- :c => [:Int8, :int8],
127
- :C => [:UInt8, :uint8],
128
- :s => [:Int16, :int16],
129
- :"s<" => [:Int16_LE, :int16_le],
130
- :"s>" => [:Int16_BE, :int16_be],
131
- :S => [:UInt16, :uint16],
132
- :"S<" => [:UInt16_LE, :uint16_le],
133
- :"S>" => [:UInt16_BE, :uint16_be],
134
- :v => [:UInt16_LE, :uint16_le],
135
- :n => [:UInt16_BE, :uint16_be],
136
- :l => [:Int32, :int32],
137
- :"l<" => [:Int32_LE, :int32_le],
138
- :"l>" => [:Int32_BE, :int32_be],
139
- :L => [:UInt32, :uint32],
140
- :"L<" => [:UInt32_LE, :uint32_le],
141
- :"L>" => [:UInt32_BE, :uint32_be],
142
- :V => [:UInt32_LE, :uint32_le],
143
- :N => [:UInt32_BE, :uint32_be],
144
- :q => [:Int64, :int64],
145
- :"q<" => [:Int64_LE, :int64_le],
146
- :"q>" => [:Int64_BE, :int64_be],
147
- :Q => [:UInt64, :uint64],
148
- :"Q<" => [:UInt64_LE, :uint64_le],
149
- :"Q>" => [:UInt64_BE, :uint64_be],
150
- :F => [:Flt, :float],
151
- :e => [:Flt_LE, :float_le],
152
- :g => [:Flt_BE, :float_be],
153
- :D => [:Double, :double],
154
- :E => [:Double_LE, :double_le],
155
- :G => [:Double_BE, :double_be],
156
- :half => [:Half, :half],
157
- :half_le => [:Half_LE, :half_le],
158
- :half_be => [:Half_BE, :half_be],
159
- :pghalf => [:PGHalf, :pghalf],
160
- :pghalf_le => [:PGHalf_LE, :pghalf_le],
161
- :pghalf_be => [:PGHalf_BE, :pghalf_be]
162
- }
163
-
164
- DATA_SIZES = Hash::new { |h,k|
165
- if k.kind_of?(Symbol) && m = k.match(/a(\d+)/)
166
- m[1].to_i
167
- else
168
- nil
169
- end
170
- }
171
- DATA_SIZES.merge!( {
172
- :c => 1,
173
- :C => 1,
174
- :s => 2,
175
- :"s<" => 2,
176
- :"s>" => 2,
177
- :S => 2,
178
- :"S<" => 2,
179
- :"S>" => 2,
180
- :v => 2,
181
- :n => 2,
182
- :l => 4,
183
- :"l<" => 4,
184
- :"l>" => 4,
185
- :L => 4,
186
- :"L<" => 4,
187
- :"L>" => 4,
188
- :V => 4,
189
- :N => 4,
190
- :q => 8,
191
- :"q<" => 8,
192
- :"q>" => 8,
193
- :Q => 8,
194
- :"Q<" => 8,
195
- :"Q>" => 8,
196
- :F => 4,
197
- :e => 4,
198
- :g => 4,
199
- :D => 8,
200
- :E => 8,
201
- :G => 8,
202
- :a => 1,
203
- :"a*" => -1,
204
- :half => 2,
205
- :half_le => 2,
206
- :half_be => 2,
207
- :pghalf => 2,
208
- :pghalf_le => 2,
209
- :pghalf_be => 2
210
- } )
211
- DATA_ENDIAN = {
212
- true => Hash::new { |h,k|
213
- if k.kind_of?(Symbol) && m = k.match(/a(\d+)/)
214
- l[k]
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
+ }
215
409
  else
216
- nil
410
+ n = @map_to[v]
411
+ n = v unless n
412
+ n
217
413
  end
218
- },
219
- false => Hash::new { |h,k|
220
- if k.kind_of?(Symbol) && m = k.match(/a(\d+)/)
221
- l[k]
414
+ end
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
+ }
222
437
  else
223
- nil
438
+ n = @map_to[v]
439
+ n = v unless n
440
+ n
224
441
  end
225
- }
226
- }
442
+ end
227
443
 
228
- rhl = lambda { |type, str, number = nil|
229
- if number
230
- number.times.collect { |i| LibBin::half_from_string(str[i*2,2], type) }
231
- else
232
- LibBin::half_from_string(str, type)
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)
233
467
  end
234
- }
468
+ end
235
469
 
236
- shl = lambda { |type, value, number = nil|
237
- if number
238
- str = ""
239
- number.times { |i| str << LibBin::half_to_string(value[i], type) }
240
- str
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
241
491
  else
242
- LibBin::half_to_string(value, type)
492
+ raise "alignement must be a power of 2" if align && (align - 1) & align != 0
243
493
  end
244
- }
494
+ @fields.push(Field::new(name, klass, length, count, offset, sequence, condition, relative_offset, align, expect))
495
+ attr_accessor name
496
+ self
497
+ end
245
498
 
246
- hl = lambda { |type|
247
- [rhl.curry[type], shl.curry[type]]
248
- }
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
249
520
 
250
- rpghl = lambda { |type, str, number = nil|
251
- if number
252
- number.times.collect { |i| LibBin::pghalf_from_string(str[i*2,2], type) }
253
- else
254
- LibBin::pghalf_from_string(str, type)
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
255
545
  end
256
- }
257
546
 
258
- spghl = lambda { |type, value, number = nil|
259
- if number
260
- str = ""
261
- number.times { |i| str << LibBin::pghalf_to_string(value[i], type) }
262
- str
263
- else
264
- LibBin::pghalf_to_string(value, type)
265
- end
266
- }
267
-
268
- pghl = lambda { |type|
269
- [rpghl.curry[type], spghl.curry[type]]
270
- }
271
-
272
- DATA_ENDIAN[true].merge!( {
273
- :c => l["c"],
274
- :C => l["C"],
275
- :s => l["s>"],
276
- :"s<" => l["s<"],
277
- :"s>" => l["s>"],
278
- :S => l["S>"],
279
- :"S<" => l["S<"],
280
- :"S>" => l["S>"],
281
- :v => l["v"],
282
- :n => l["n"],
283
- :l => l["l>"],
284
- :"l<" => l["l<"],
285
- :"l>" => l["l>"],
286
- :L => l["L>"],
287
- :"L<" => l["L<"],
288
- :"L>" => l["L>"],
289
- :V => l["V"],
290
- :N => l["N"],
291
- :q => l["q>"],
292
- :"q<" => l["q<"],
293
- :"q>" => l["q>"],
294
- :Q => l["Q>"],
295
- :"Q<" => l["Q<"],
296
- :"Q>" => l["Q>"],
297
- :F => l["g"],
298
- :e => l["e"],
299
- :g => l["g"],
300
- :D => l["G"],
301
- :E => l["E"],
302
- :G => l["G"],
303
- :half => hl["S>"],
304
- :half_le => hl["S<"],
305
- :half_be => hl["S>"],
306
- :pghalf => pghl["S>"],
307
- :pghalf_le => pghl["S<"],
308
- :pghalf_be => pghl["S>"]
309
- } )
310
- DATA_ENDIAN[false].merge!( {
311
- :c => l["c"],
312
- :C => l["C"],
313
- :s => l["s<"],
314
- :"s<" => l["s<"],
315
- :"s>" => l["s>"],
316
- :S => l["S<"],
317
- :"S<" => l["S<"],
318
- :"S>" => l["S>"],
319
- :v => l["v"],
320
- :n => l["n"],
321
- :l => l["l<"],
322
- :"l<" => l["l<"],
323
- :"l>" => l["l>"],
324
- :L => l["L<"],
325
- :"L<" => l["L<"],
326
- :"L>" => l["L>"],
327
- :V => l["V"],
328
- :N => l["N"],
329
- :q => l["q<"],
330
- :"q<" => l["q<"],
331
- :"q>" => l["q>"],
332
- :Q => l["Q<"],
333
- :"Q<" => l["Q<"],
334
- :"Q>" => l["Q>"],
335
- :F => l["e"],
336
- :e => l["e"],
337
- :g => l["g"],
338
- :D => l["E"],
339
- :E => l["E"],
340
- :G => l["G"],
341
- :half => hl["S<"],
342
- :half_le => hl["S<"],
343
- :half_be => hl["S>"],
344
- :pghalf => pghl["S<"],
345
- :pghalf_le => pghl["S<"],
346
- :pghalf_be => pghl["S>"]
347
- } )
348
-
349
-
350
- class Scalar
351
-
352
- def self.size(*args)
353
- @size
354
- end
355
-
356
- def self.shape(value, previous_offset = 0, _ = nil, _ = nil, kind = DataShape, length = nil)
357
- length = 1 unless length
358
- kind::new(previous_offset, previous_offset - 1 + length * @size)
359
- end
360
-
361
- def self.init(symbol)
362
- @symbol = symbol
363
- @size = DATA_SIZES[symbol]
364
- @rl_be, @sl_be = DATA_ENDIAN[true][symbol]
365
- @rl_le, @sl_le = DATA_ENDIAN[false][symbol]
366
- end
367
-
368
- def self.load(input, input_big = LibBin::default_big?, _ = nil, _ = nil, length = nil)
369
- l = (length ? length : 1)
370
- str = input.read(@size*l)
371
- input_big ? @rl_be[str, length] : @rl_le[str, length]
372
- end
373
-
374
- def self.dump(value, output, output_big = LibBin::default_big?, _ = nil, _ = nil, length = nil)
375
- str = (output_big ? @sl_be[value, length] : @sl_le[value, length])
376
- output.write(str)
377
- end
378
-
379
- def self.convert(input, output, input_big = LibBin::default_big?, output_big = !input_big, _ = nil, _ = nil, length = nil)
380
- l = (length ? length : 1)
381
- str = input.read(@size*l)
382
- value = (input_big ? @rl_be[str, length] : @rl_le[str, length])
383
- str = (output_big ? @sl_be[value, length] : @sl_le[value, length])
384
- output.write(str)
385
- value
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
386
562
  end
387
563
 
388
- end
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
389
599
 
390
- class Str < Scalar
600
+ # Returns the underlying type +align+ property
601
+ def self.align
602
+ @type.align
603
+ end
391
604
 
392
- def self.size(value, previous_offset = 0, parent = nil, index = nil, length = nil)
393
- length ? length : value.size
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)
394
608
  end
395
609
 
396
- def self.load(input, input_big = LibBin::default_big?, _ = nil, _ = nil, length = nil)
397
- str = (length ? input.read(length) : input.readline("\x00"))
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)
398
613
  end
399
614
 
400
- def self.convert(input, output, input_big = LibBin::default_big?, output_big = !LibBin::default_big, _ = nil, _ = nil, length = nil)
401
- str = (length ? input.read(length) : input.readline("\x00"))
402
- output.write(str)
403
- str
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
404
637
  end
405
638
 
406
- def self.shape(value, previous_offset = 0, _ = nil, _ = nil, kind = DataShape, length = nil)
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)
407
653
  if length
408
- kind::new(previous_offset, previous_offset + length - 1)
654
+ v.collect { |val|
655
+ bf = self.new
656
+ bf.__set_value(val)
657
+ bf
658
+ }
409
659
  else
410
- kind::new(previous_offset, previous_offset + value.size - 1)
660
+ bf = self.new
661
+ bf.__set_value(v)
662
+ bf
411
663
  end
412
664
  end
413
665
 
414
- def self.dump(value, output, output_big = LibBin::default_big?, _ = nil, _ = nil, length = nil)
415
- output.write(value)
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)
416
687
  end
417
688
  end
418
689
 
419
- def self.register_field(field, type, length: nil, count: nil, offset: nil, sequence: false, condition: nil, relative_offset: false)
420
- if type.kind_of?(Symbol)
421
- if type[0] == 'a'
422
- real_type = Class::new(Str) do init(sym) end
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
423
706
  else
424
- real_type = const_get(SCALAR_TYPES[type][0])
707
+ align = al
425
708
  end
709
+ end
710
+ if align == true
711
+ align = klass.align
426
712
  else
427
- real_type = type
713
+ raise "alignement must be a power of 2" if align && (align - 1) & align != 0
428
714
  end
429
- @fields.push(Field::new(field, real_type, length, count, offset, sequence, condition, relative_offset))
430
- attr_accessor field
431
- end
432
-
433
- def self.create_scalar_type(symbol)
434
- klassname, name = SCALAR_TYPES[symbol]
435
- eval <<EOF
436
- class #{klassname} < Scalar
437
- init(#{symbol.inspect})
438
- end
439
-
440
- def self.#{name}(field, length: nil, count: nil, offset: nil, sequence: false, condition: nil, relative_offset: false)
441
- @fields.push(Field::new(field, #{klassname}, length, count, offset, sequence, condition, relative_offset))
442
- attr_accessor field
443
- end
444
- EOF
445
- end
446
-
447
- create_scalar_type(:c)
448
- create_scalar_type(:C)
449
- create_scalar_type(:s)
450
- create_scalar_type(:"s<")
451
- create_scalar_type(:"s>")
452
- create_scalar_type(:S)
453
- create_scalar_type(:"S<")
454
- create_scalar_type(:"S>")
455
- create_scalar_type(:l)
456
- create_scalar_type(:"l<")
457
- create_scalar_type(:"l>")
458
- create_scalar_type(:L)
459
- create_scalar_type(:"L<")
460
- create_scalar_type(:"L>")
461
- create_scalar_type(:q)
462
- create_scalar_type(:"q<")
463
- create_scalar_type(:"q>")
464
- create_scalar_type(:Q)
465
- create_scalar_type(:"Q<")
466
- create_scalar_type(:"Q>")
467
- create_scalar_type(:F)
468
- create_scalar_type(:e)
469
- create_scalar_type(:g)
470
- create_scalar_type(:D)
471
- create_scalar_type(:E)
472
- create_scalar_type(:G)
473
- create_scalar_type(:half)
474
- create_scalar_type(:half_le)
475
- create_scalar_type(:half_be)
476
- create_scalar_type(:pghalf)
477
- create_scalar_type(:pghalf_le)
478
- create_scalar_type(:pghalf_be)
479
-
480
- def self.string( field, length = nil, count: nil, offset: nil, sequence: false, condition: nil, relative_offset: false)
481
- sym = (length ? :"a" : :"a*")
482
- c = Class::new(Str) do
483
- init(sym)
484
- end
485
- @fields.push(Field::new(field, c, length, count, offset, sequence, condition, relative_offset))
486
- attr_accessor field
715
+ @fields.push(Field::new(name, klass, length, count, offset, sequence, condition, relative_offset, align, expect))
716
+ attr_accessor name
717
+ self
487
718
  end
488
719
 
489
720
  end