ffi-struct_ex 0.0.3 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 640a03806e45782319f75058970cf5c999927d03
4
- data.tar.gz: f5f3247405c6243e66a4d958d356787cb231c7d8
3
+ metadata.gz: f351f025099f8a13f3c2aeba992b7fd67d8dd90b
4
+ data.tar.gz: 198834f90618214c7ffb3a811d3de12c114dd4db
5
5
  SHA512:
6
- metadata.gz: 57dcf4abe3d98e2fe8c0cbcbaed6e17e2ac2a8d877f1c57d53da3f97666099e73de65cca320714451e535ab0a5724361d6c3bbc965838d50d7a58209a4779867
7
- data.tar.gz: ece4cba95fbf5095e5151432f072957ed6d3d8eaf4b6e56ace473d745dfa48ccd12c4531464a6cb185698012b1cb942769181e2bcb92649f83023c76378ef8b0
6
+ metadata.gz: 31828f46959ae2d4a4686b787f0555b3f320c12bfb80d7375920d1a45e8e620a27dc1f75cf13a19233ea157ac208e22f7e1909305cf156ca0b66894e256e22c5
7
+ data.tar.gz: daf79112bfa648f3aa9343319cf5e21d2cae9668ef1df5a98544e4af347279408032267184325fa080c7bd031446fcc8583327de83bf6b259cbd84d3b3281b39
data/README.md CHANGED
@@ -18,22 +18,24 @@ Or install it yourself as:
18
18
 
19
19
  ## Usage
20
20
 
21
- * Struct (only support "unsigned" field)
21
+ * Struct (default type is "unsigned char/short/int" depending on bits size)
22
22
 
23
23
  ```ruby
24
24
  require 'ffi/struct_ex'
25
25
 
26
26
  class Subject < FFI::StructEx
27
- layout :bits_0_2, 3,
28
- :bit_3, 1,
27
+ layout :bits_0_2, 'uint8: 3',
28
+ :bit_3, 'char: 1',
29
29
  :bit_4, 1,
30
- :bits_5_7, 3
30
+ :bits_5_7, 'char: 3'
31
31
  end
32
32
 
33
- subject = Subject.new(bits_0_2: 0b001, bit_3: 0b1, bit_4: 0b0, bits_5_7: 0b011)
33
+ Subject.size #=> 1
34
34
 
35
- subject[:bits_0_2] #=> 0b101
36
- subject.read #=> 0b0110_1001
35
+ subject = Subject.new(bits_0_2: 0b001, bit_3: 0b1, bit_4: 0b1, bits_5_7: 0b100)
36
+
37
+ subject[:bits_0_2] #=> 1
38
+ subject[:bits_5_7] #=> -4
37
39
  ```
38
40
 
39
41
  * Struct (embedded bit fields)
@@ -9,6 +9,10 @@ class Integer
9
9
 
10
10
  bytes
11
11
  end
12
+
13
+ def to_signed(bits_size)
14
+ self & (1 << bits_size - 1) != 0 ? self - (1 << bits_size) : self
15
+ end
12
16
  end
13
17
 
14
18
  class String
@@ -26,67 +30,140 @@ end
26
30
 
27
31
  module FFI
28
32
  class StructEx < FFI::Struct
33
+ SIGNED_NUMBER_TYPES = [FFI::Type::INT8, FFI::Type::INT16, FFI::Type::INT32, FFI::Type::INT64]
34
+ UNSIGNED_NUMBER_TYPES = [FFI::Type::UINT8, FFI::Type::UINT16, FFI::Type::UINT32, FFI::Type::UINT64]
35
+
29
36
  class << self
30
37
  def bit_fields(*field_specs)
31
- struct_class = Class.new(StructEx) do
32
- layout(*field_specs)
33
- end
38
+ Class.new(FFI::StructLayout::Field) do
39
+ class << self
40
+ attr_accessor :struct_class
41
+
42
+ def alignment; struct_class.alignment; end
43
+ def size; struct_class.size; end
44
+ end
45
+
46
+ self.struct_class = Class.new(StructEx) do
47
+ layout(*field_specs)
48
+ end
34
49
 
35
- bit_fields_class = Class.new(FFI::StructLayout::Field) do
36
50
  def initialize(name, offset, type)
37
51
  super(name, offset, FFI::Type::Struct.new(self.class.struct_class))
38
52
  end
39
53
 
40
54
  def get(ptr)
41
- #self.class.struct_class == type.struct_class
42
- self.class.struct_class.new(ptr.slice(self.offset, self.size))
55
+ type.struct_class.new(ptr.slice(offset, size))
43
56
  end
44
57
 
45
58
  def put(ptr, value)
46
- self.class.struct_class.new(ptr.slice(self.offset, self.size)).write(value)
59
+ type.struct_class.new(ptr.slice(offset, size)).write(value)
47
60
  end
61
+ end
62
+ end
48
63
 
64
+ def bit_field(type, bits_size, bits_offset)
65
+ Class.new(FFI::StructLayout::Field) do
49
66
  class << self
50
- attr_accessor :struct_class
67
+ attr_accessor :type, :bits_size, :bits_offset, :struct_class
68
+ #no need to implement alignment b/c we always provide offset when adding this field to struct_layout_builder
69
+ end
51
70
 
52
- def alignment
53
- struct_class.alignment
54
- end
71
+ self.struct_class = Class.new(Struct) do
72
+ layout('', type)
73
+ end
55
74
 
56
- def size
57
- struct_class.size
58
- end
75
+ self.type, self.bits_size, self.bits_offset = type, bits_size, bits_offset
76
+
77
+ def initialize(name, offset, type)
78
+ super(name, offset, FFI::Type::Struct.new(self.class.struct_class))
59
79
  end
60
80
 
61
- self.struct_class = struct_class
81
+ def read(ptr)
82
+ ptr.slice(offset, size).send("read_uint#{size * 8}".to_sym)
83
+ end
84
+
85
+ def write(ptr, value)
86
+ ptr.slice(offset, size).send("write_uint#{size * 8}".to_sym, value)
87
+ end
88
+
89
+ def get(ptr)
90
+ mask = (1 << self.class.bits_size) - 1
91
+ value = (read(ptr) >> self.class.bits_offset) & mask
92
+
93
+ SIGNED_NUMBER_TYPES.include?(self.class.type) ? value.to_signed(self.class.bits_size) : value
94
+ end
95
+
96
+ def put(ptr, value)
97
+ mask = ((1 << self.class.bits_size) - 1) << self.class.bits_offset
98
+ write(ptr, (read(ptr) & ~mask) | ((value << self.class.bits_offset) & mask))
99
+ end
62
100
  end
63
101
  end
64
102
 
65
- attr_reader :bits_size, :field_specs, :has_bit_field
103
+ attr_reader :field_specs
66
104
 
67
- def layout(*field_specs)
68
- return super if field_specs.size == 0
105
+ private
106
+ def array_layout(builder, field_specs)
107
+ @field_specs = {}
69
108
 
70
- field_spec_class = ::Struct.new(:name, :type, :bits_offset, :descriptors)
109
+ field_spec_class = ::Struct.new(:name, :type, :descriptors)
71
110
 
72
- @field_specs = {}
111
+ current_allocation_unit = nil
73
112
 
74
- i = bits_offset = 0
113
+ offset = i = 0
75
114
 
76
115
  while i < field_specs.size
77
- field_name, type = field_specs[i, 2]
116
+ name, type = field_specs[i, 2]
78
117
  i += 2
79
118
 
80
- unless type.is_a?(Integer)
81
- type = find_field_type(type)
82
- bits_size = type.size * 8
83
-
119
+ unless type.is_a?(Integer) || type.is_a?(String)
120
+ # If the next param is a Integer, it specifies the offset
84
121
  if field_specs[i].is_a?(Integer)
85
- bits_offset = field_specs[i] * 8
122
+ offset = field_specs[i]
86
123
  i += 1
124
+ else
125
+ offset = nil
87
126
  end
127
+
128
+ type = find_field_type(type)
129
+ builder.add name, type, offset
130
+
131
+ current_allocation_unit = nil
88
132
  else
89
- bits_size = type
133
+ if type.is_a?(Integer)
134
+ ffi_type, bits_size = UNSIGNED_NUMBER_TYPES.find {|ffi_type| type <= ffi_type.size * 8}, type
135
+ raise "Unrecognized format #{type}" unless ffi_type
136
+ elsif type.is_a?(String)
137
+ m = /^(?<ffi_type>[\w_]+)\s*:\s*(?<bits_size>\d+)$/.match(type.strip)
138
+ raise "Unrecognized format #{type}" unless m
139
+
140
+ ffi_type, bits_size = find_field_type(m[:ffi_type].to_sym), m[:bits_size].to_i
141
+ raise "Unrecognized type #{type}" unless UNSIGNED_NUMBER_TYPES.include?(ffi_type) || SIGNED_NUMBER_TYPES.include?(ffi_type)
142
+ end
143
+
144
+ raise "Illegal format #{type}" if bits_size > ffi_type.size * 8
145
+
146
+ unless current_allocation_unit
147
+ current_allocation_unit = {ffi_type: ffi_type, bits_size: bits_size}
148
+ offset = builder.send(:align, builder.size, [@min_alignment || 1, ffi_type.alignment].max)
149
+ else
150
+ # Adjacent bit fields are packed into the same 1-, 2-, or 4-byte allocation unit if the integral types are the same size
151
+ # and if the next bit field fits into the current allocation unit without crossing the boundary
152
+ # imposed by the common alignment requirements of the bit fields.
153
+ if ffi_type.size == current_allocation_unit[:ffi_type].size
154
+ if current_allocation_unit[:bits_size] + bits_size <= ffi_type.size * 8
155
+ current_allocation_unit[:bits_size] += bits_size
156
+ else
157
+ offset = builder.send(:align, builder.size, [@min_alignment || 1, ffi_type.alignment].max)
158
+ current_allocation_unit[:bits_size] = bits_size
159
+ end
160
+ else
161
+ offset = builder.send(:align, builder.size, [@min_alignment || 1, ffi_type.alignment].max)
162
+ current_allocation_unit = {ffi_type: ffi_type, bits_size: bits_size}
163
+ end
164
+ end
165
+
166
+ builder.add name, find_field_type(bit_field(ffi_type, bits_size, current_allocation_unit[:bits_size] - bits_size)), offset
90
167
  end
91
168
 
92
169
  if field_specs[i].is_a?(Hash)
@@ -96,18 +173,7 @@ module FFI
96
173
  descriptors = {}
97
174
  end
98
175
 
99
- @field_specs[field_name] = field_spec_class.new(field_name, type, bits_offset, descriptors)
100
- bits_offset += bits_size
101
- end
102
-
103
- @has_bit_field = @field_specs.any? {|field_name, field_spec| field_spec.type.is_a?(Integer)}
104
-
105
- if @has_bit_field
106
- #FIXME consider 24 bits situation or larger than 32 bits
107
- #FIXME remove dummy field or have a better name for this field
108
- super(:dummy, "uint#{(bits_offset + 7) & (-1 << 3)}".to_sym)
109
- else
110
- super(*field_specs.reject {|field_spec| field_spec.is_a?(Hash)})
176
+ @field_specs[name] = field_spec_class.new(name, type, descriptors)
111
177
  end
112
178
  end
113
179
  end
@@ -121,25 +187,9 @@ module FFI
121
187
  end
122
188
  end
123
189
 
124
- def [](field_name)
125
- return super unless self.class.has_bit_field
126
-
127
- field_spec = self.class.field_specs[field_name]
128
- mask = ((1 << field_spec.type) - 1) << field_spec.bits_offset
129
-
130
- (self.read & mask) >> field_spec.bits_offset
131
- end
132
-
133
190
  # Set field value
134
191
  def []=(field_name, value)
135
- value = map_field_value(field_name, value)
136
-
137
- return super(field_name, value) unless self.class.has_bit_field
138
-
139
- field_spec = self.class.field_specs[field_name]
140
- mask = ((1 << field_spec.type) - 1) << field_spec.bits_offset
141
-
142
- self.write((self.read & (-1 - mask)) | ((value << field_spec.bits_offset) & mask))
192
+ super(field_name, map_field_value(field_name, value))
143
193
  end
144
194
 
145
195
  def write(value)
@@ -185,7 +235,7 @@ module FFI
185
235
  return field_spec.descriptors[descriptor_key] if field_spec.descriptors.has_key?(descriptor_key)
186
236
 
187
237
  type = field_spec.type
188
- return value.to_dec if (type.is_a?(Integer) || FFI::StructLayoutBuilder::NUMBER_TYPES.include?(type)) && value.is_a?(String)
238
+ return value.to_dec if (type.is_a?(Integer) || type.is_a?(String) || FFI::StructLayoutBuilder::NUMBER_TYPES.include?(type)) && value.is_a?(String)
189
239
 
190
240
  value
191
241
  end
@@ -1,5 +1,5 @@
1
1
  module Ffi
2
2
  module StructEx
3
- VERSION = "0.0.3"
3
+ VERSION = "0.1.0"
4
4
  end
5
5
  end
@@ -5,11 +5,11 @@ require 'ffi/struct_ex'
5
5
  class TestStructEx < Test::Unit::TestCase
6
6
  def test_bit_fields
7
7
  subject_class = Class.new(FFI::StructEx) do
8
- layout :field_0, bit_fields(:bits_0_2, 3,
9
- :bit_3, 1,
10
- :bit_4, 1,
11
- :bits_5_7, 3,
12
- :bits_8_15, 8),
8
+ layout :field_0, bit_fields(:bits_0_2, 'uint16: 3',
9
+ :bit_3, 'uint16: 1',
10
+ :bit_4, 'uint16: 1',
11
+ :bits_5_7, 'uint16: 3',
12
+ :bits_8_15, 'uint16: 8'),
13
13
  :field_1, :uint8,
14
14
  :field_2, :uint8,
15
15
  :field_3, :uint8
@@ -23,6 +23,7 @@ class TestStructEx < Test::Unit::TestCase
23
23
 
24
24
  assert_equal(FFI::StructEx, subject[:field_0].class.superclass)
25
25
  assert_equal(2, subject[:field_0].size)
26
+ assert_equal(2, subject[:field_0].alignment)
26
27
 
27
28
  subject[:field_0] = 0b0110_1001
28
29
  assert_equal(0b0110_1001, subject[:field_0].read)
@@ -122,8 +123,8 @@ class TestStructEx < Test::Unit::TestCase
122
123
 
123
124
  assert_equal(FFI::StructEx, subject[:field_0].class.superclass)
124
125
  assert_equal(1, subject[:field_0].size)
125
- assert(subject[:field_0] == 0b0110_1001)
126
- assert(subject[:field_1] == subject.map_field_value(:field_1, '0x1'))
126
+ assert_equal(0b0110_1001, subject[:field_0])
127
+ assert_equal(subject[:field_1], subject.map_field_value(:field_1, '0x1'))
127
128
  end
128
129
 
129
130
  def test_descriptors
@@ -166,12 +167,181 @@ class TestStructEx < Test::Unit::TestCase
166
167
  assert_equal(0x00, subject[:field_1])
167
168
  end
168
169
 
169
- # def test_sizeof
170
- # subject_class = Class.new(FFI::StructEx) do
171
- # layout :field_0, 2,
172
- # :field_1, 31
173
- # end
174
- #
175
- # assert_equal(8, subject_class.size)
176
- # end
170
+ def test_sizeof
171
+ assert_equal(4, Class.new(FFI::StructEx) do
172
+ layout :field_0, :short,
173
+ :field_1, :char,
174
+ :field_2, :char
175
+ end.size)
176
+
177
+ assert_equal(8, Class.new(FFI::StructEx) do
178
+ layout :field_0, 31,
179
+ :field_1, 31
180
+ end.size)
181
+
182
+ assert_equal(8, Class.new(FFI::StructEx) do
183
+ layout :field_0, 31,
184
+ :field_1, :uint8
185
+ end.size)
186
+
187
+ assert_equal(8, Class.new(FFI::StructEx) do
188
+ layout :field_0, 1,
189
+ :field_1, :uint32
190
+ end.size)
191
+
192
+ assert_equal(3, Class.new(FFI::StructEx) do
193
+ layout :field_0, 4,
194
+ :field_1, 4,
195
+ :field_2, 8,
196
+ :field_3, :uint8
197
+ end.size)
198
+
199
+ assert_equal(2, Class.new(FFI::StructEx) do
200
+ layout :field_0, 1,
201
+ :field_1, 1,
202
+ :field_2, :uint8
203
+ end.size)
204
+
205
+ assert_equal(3, Class.new(FFI::StructEx) do
206
+ layout :field_0, 1,
207
+ :field_1, 1,
208
+ :field_2, :uint8,
209
+ :field_3, 1,
210
+ :field_4, 1
211
+ end.size)
212
+
213
+ assert_equal(6, Class.new(FFI::StructEx) do
214
+ layout :field_0, 1,
215
+ :field_1, 1,
216
+ :field_2, :uint16,
217
+ :field_3, 1,
218
+ :field_4, 1
219
+ end.size)
220
+
221
+ assert_equal(6, Class.new(FFI::StructEx) do
222
+ layout :field_0, 1,
223
+ :field_1, 1,
224
+ :field_2, :uint16,
225
+ :field_3, 1,
226
+ :field_4, 8
227
+ end.size)
228
+
229
+ assert_equal(4, Class.new(FFI::StructEx) do
230
+ layout :field_0, 8,
231
+ :field_1, 16
232
+ end.size)
233
+
234
+ assert_equal(4, Class.new(FFI::StructEx) do
235
+ layout :field_0, 16,
236
+ :field_1, 8
237
+ end.size)
238
+
239
+ assert_equal(6, Class.new(FFI::StructEx) do
240
+ layout :field_0, 1,
241
+ :field_1, 16,
242
+ :field_2, 1
243
+ end.size)
244
+
245
+ assert_equal(1, Class.new(FFI::StructEx) do
246
+ layout :field_0, 'uint8: 1'
247
+ end.size)
248
+
249
+ assert_equal(2, Class.new(FFI::StructEx) do
250
+ layout :field_0, 'uint16: 1'
251
+ end.size)
252
+
253
+ assert_equal(4, Class.new(FFI::StructEx) do
254
+ layout :field_0, 'uint32: 1'
255
+ end.size)
256
+
257
+ assert_equal(2, Class.new(FFI::StructEx) do
258
+ layout :bits_0_2, 'uint16: 3',
259
+ :bit_3, 'uint16: 1',
260
+ :bit_4, 'uint16: 1',
261
+ :bits_5_7, 'uint16: 3',
262
+ :bits_8_15, 'uint16: 8'
263
+ end.size)
264
+
265
+ assert_equal(1, Class.new(FFI::StructEx) do
266
+ layout :field_0, 'uint8: 1',
267
+ :field_1, 'uint8: 1'
268
+ end.size)
269
+
270
+ assert_equal(1, Class.new(FFI::StructEx) do
271
+ layout :field_0, 'uint8: 1',
272
+ :field_1, 'int8: 1'
273
+ end.size)
274
+
275
+ assert_equal(8, Class.new(FFI::StructEx) do
276
+ layout :field_0, 'uint32: 1',
277
+ :field_1, :uint16
278
+ end.size)
279
+
280
+ assert_equal(8, Class.new(FFI::StructEx) do
281
+ layout :field_0, 'uint8: 1',
282
+ :field_1, :uint32
283
+ end.size)
284
+
285
+ assert_equal(8, Class.new(FFI::StructEx) do
286
+ layout :field_0, 'uint8: 1',
287
+ :field_1, 'uint32: 1'
288
+ end.size)
289
+
290
+ assert_equal(4, Class.new(FFI::StructEx) do
291
+ layout :field_0, :short,
292
+ :field_1, :char
293
+ end.size)
294
+
295
+ assert_equal(4, Class.new(FFI::StructEx) do
296
+ layout :field_0, :short,
297
+ :field_1, 'char: 1'
298
+ end.size)
299
+
300
+ subject_class = Class.new(FFI::StructEx) do
301
+ layout :field_0, :char,
302
+ :field_1, 'short: 1'
303
+ end
304
+ assert_equal(4, subject_class.size)
305
+
306
+ subject_class = Class.new(FFI::StructEx) do
307
+ layout :field_0, 'uint: 8',
308
+ :field_1, 'int: 1'
309
+ end
310
+ assert_equal(4, subject_class.size)
311
+ subject = subject_class.new
312
+ subject[:field_0] = 0b0110_1001
313
+ assert_equal(0b0110_1001, subject[:field_0])
314
+ subject[:field_0] = 0b1111_1111
315
+ assert_equal(0b1111_1111, subject[:field_0])
316
+
317
+ subject[:field_1] = 1
318
+ assert_equal(-1, subject[:field_1])
319
+ subject[:field_1] = 0
320
+ assert_equal(0, subject[:field_1])
321
+ subject[:field_1] = -1
322
+ assert_equal(-1, subject[:field_1])
323
+
324
+ #Check no impact for typedef type
325
+ FFI.typedef :uint8, :UINT8
326
+ subject_class = Class.new(FFI::StructEx) do
327
+ layout :field_0, 'UINT8: 8',
328
+ :field_1, 'int: 1'
329
+ end
330
+
331
+ subject_class = Class.new(FFI::StructEx) do
332
+ layout :bits_0_2, 'uint8: 3',
333
+ :bit_3, 'char: 1',
334
+ :bit_4, 'uint8: 1',
335
+ :bits_5_7, 'char: 3'
336
+ end
337
+
338
+ assert_equal(1, subject_class.size)
339
+
340
+ subject = subject_class.new(bits_0_2: 0b001, bit_3: 0b1, bit_4: 0b1, bits_5_7: 0b100)
341
+ assert_equal(1, subject[:bits_0_2])
342
+ assert_equal(-1, subject[:bit_3])
343
+ assert_equal(1, subject[:bit_4])
344
+ assert_equal(-4, subject[:bits_5_7])
345
+
346
+ end
177
347
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ffi-struct_ex
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ruijia Li
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-05-22 00:00:00.000000000 Z
11
+ date: 2014-05-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ffi