libbin 1.0.5 → 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,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