ffi-bitfield 0.0.11 → 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
  SHA256:
3
- metadata.gz: 6d3f4eec570a9f6495bafce91ae4000096cbd695f260266a303d156191e1419a
4
- data.tar.gz: d60ef417ea3c97bd703c75f20372f3b94e6e98ed46f8c4e113cf97745998b0cf
3
+ metadata.gz: fe9149d50ec1010a72da77b760d5c9f110688a2fae95fa51fd8df155ea6f85f7
4
+ data.tar.gz: 6f2e4d91b7386406e89512d8a3a352c5e40a39a554ca128b70fc6c4573984dd6
5
5
  SHA512:
6
- metadata.gz: '092d78c15292d9f91382751f4a26540139aa8898e0604d464b5e672da1b07d950534b909044c049b49e2c846ec57d77928c39f5398fe0b029efad69ba7a5b277'
7
- data.tar.gz: 6e90c2a27c7139ed9948c8ba4a6dce38e7a5aeafcff78d782d05451ee8e7c428c9c13dcfe2a7f423df8eec2ad09501b33b8fa2a0edb867c857adfbdca84822c4
6
+ metadata.gz: 18912f59cc0434df26e14b211b275478c89fe2a96852728a6213ffb134e7d5a83d698d274a3258f17735c38dc7c38ef2b9dde4a62f23d6a0903b1b356be72e1d
7
+ data.tar.gz: c5cda72742bdf29f27f24512d7d3263c6700ffcfc19cff4a7d4e8687cd44f60a7f7ad4a03b4939f6a55bd30a606e3e93a63cc41dccbe52b1b24ecb62901ef59e
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2021 kojix2
3
+ Copyright (c) 2021-present kojix2
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/ffi-bitfield.svg)](https://badge.fury.io/rb/ffi-bitfield)
4
4
  [![test](https://github.com/kojix2/ffi-bitfield/actions/workflows/ci.yml/badge.svg)](https://github.com/kojix2/ffi-bitfield/actions/workflows/ci.yml)
5
+ [![Lines of Code](https://img.shields.io/endpoint?url=https%3A%2F%2Ftokei.kojix2.net%2Fbadge%2Fgithub%2Fkojix2%2Fffi-bitfield%2Flines)](https://tokei.kojix2.net/github/kojix2/ffi-bitfield)
5
6
 
6
7
  Bit field for [Ruby-FFI](https://github.com/ffi/ffi)
7
8
 
@@ -15,8 +16,8 @@ gem install ffi-bitfield
15
16
 
16
17
  Classes
17
18
 
18
- * class BitStruct < FFI::Struct
19
- * class ManagedBitStruct < FFI::ManagedStruct
19
+ - class BitStruct < FFI::Struct
20
+ - class ManagedBitStruct < FFI::ManagedStruct
20
21
 
21
22
  Loading
22
23
 
@@ -70,6 +71,35 @@ s[:y] = 1
70
71
  p s[:a] # 64
71
72
  ```
72
73
 
74
+ ### Typed Bit Fields
75
+
76
+ You can use the `bit_fields_typed` method to define bit fields with type information. This method accepts a hash where keys are field names and values are arrays containing `[width, type]`. For boolean fields (width of 1 with `:bool` type), it automatically generates a `?` helper method:
77
+
78
+ ```ruby
79
+ class AccessFlags < FFI::BitStruct
80
+ layout \
81
+ :flags, :uint8
82
+
83
+ bit_fields_typed :flags,
84
+ revoked: [1, :bool], # Creates revoked? method
85
+ expired: [1, :bool], # Creates expired? method
86
+ some_string: [4, :string],
87
+ reserved: [2, :int]
88
+ end
89
+
90
+ flags = AccessFlags.new
91
+ flags[:revoked] = 1
92
+ flags[:expired] = 0
93
+
94
+ p flags.revoked? # => true
95
+ p flags.expired? # => false
96
+
97
+ flags[:expired] = 1
98
+ p flags.expired? # => true
99
+ ```
100
+
101
+ The `?` methods are only generated for fields that are 1 bit wide and have type `:bool`. Other field types can be used for documentation purposes and future functionality.
102
+
73
103
  ### Inspecting Bit Fields
74
104
 
75
105
  You can use the `bit_field_members` method to get a hash of bit fields grouped by parent field:
@@ -128,7 +158,7 @@ bundle install
128
158
  bundle exec rake test
129
159
  ```
130
160
 
131
- * [ffi-bitfield - read/write bit fields with Ruby-FFI](https://dev.to/kojix2/ffi-bitfield-g4h)
161
+ - [ffi-bitfield - read/write bit fields with Ruby-FFI](https://dev.to/kojix2/ffi-bitfield-g4h)
132
162
 
133
163
  ## Contributing
134
164
 
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  module FFI
4
2
  module BitField
5
3
  # ClassMethods provides methods for defining bit field layouts.
@@ -147,9 +145,13 @@ module FFI
147
145
  member_names << name.to_sym
148
146
  widths << width.to_i
149
147
  end
150
- starts = widths.inject([0]) do |result, width|
151
- result << (result.last + width)
152
- end
148
+
149
+ # Prevent redefining bit fields on the same parent field
150
+ ensure_parent_not_defined(parent_name)
151
+
152
+ # Validate total width against parent size
153
+ validate_total_width(parent_name, widths)
154
+ starts = bit_start_offsets(widths)
153
155
  member_names.zip(starts, widths).each do |name, start, width|
154
156
  @bit_field_hash_table[name] = [parent_name, start, width]
155
157
  end
@@ -157,6 +159,97 @@ module FFI
157
159
  parent_name
158
160
  end
159
161
  alias bit_field bit_fields
162
+
163
+ # Defines typed bit fields within a parent field using hash syntax.
164
+ #
165
+ # @param [Symbol] parent_name The name of the parent field
166
+ # @param [Hash] field_definitions A hash where keys are field names and values are arrays [width, type]
167
+ # @return [Symbol] parent_name The name of the parent field
168
+ #
169
+ # @example Define typed bit fields with automatic boolean helpers
170
+ # bit_fields_typed :flags,
171
+ # revoked: [1, :bool], # Creates revoked? methods
172
+ # expired: [1, :bool], # Creates expired? methods
173
+ # some_string: [4, :string] # Creates some_string method
174
+ #
175
+ # @note For fields with width 1 and type :bool, a "?" helper method is automatically created
176
+ # @note The total bit width should not exceed the size of the parent field.
177
+ def bit_fields_typed(parent_name, field_definitions)
178
+ @bit_field_hash_table = {} unless instance_variable_defined?(:@bit_field_hash_table)
179
+ @bit_field_type_table = {} unless instance_variable_defined?(:@bit_field_type_table)
180
+
181
+ parent_name = parent_name.to_sym
182
+ member_names = []
183
+ widths = []
184
+ types = []
185
+
186
+ field_definitions.each do |name, definition|
187
+ width, type = definition
188
+ member_names << name.to_sym
189
+ widths << width.to_i
190
+ types << type.to_sym
191
+ end
192
+
193
+ # Prevent redefining bit fields on the same parent field
194
+ ensure_parent_not_defined(parent_name)
195
+
196
+ # Validate total width against parent size
197
+ validate_total_width(parent_name, widths)
198
+
199
+ starts = bit_start_offsets(widths)
200
+
201
+ member_names.zip(starts, widths, types).each do |name, start, width, type|
202
+ @bit_field_hash_table[name] = [parent_name, start, width]
203
+ @bit_field_type_table[name] = type
204
+
205
+ # Generate "?" method for boolean fields with width 1
206
+ next unless width == 1 && type == :bool
207
+
208
+ define_method(:"#{name}?") do
209
+ self[name] == 1
210
+ end
211
+ end
212
+
213
+ parent_name
214
+ end
215
+
216
+ private
217
+
218
+ # Return cumulative start positions for given widths
219
+ # e.g., [3, 4, 1] => [0, 3, 7]
220
+ def bit_start_offsets(widths)
221
+ starts = []
222
+ sum = 0
223
+ widths.each do |w|
224
+ starts << sum
225
+ sum += w
226
+ end
227
+ starts
228
+ end
229
+
230
+ # Raise if the same parent already has bit fields
231
+ def ensure_parent_not_defined(parent_name)
232
+ return unless @bit_field_hash_table&.any? { |_n, info| info[0] == parent_name }
233
+
234
+ raise ArgumentError, "bit_fields for :#{parent_name} already defined"
235
+ end
236
+
237
+ # Return parent field size in bits (nil if unknown)
238
+ def parent_size_bits(parent_name)
239
+ field = layout[parent_name] # nil if not found
240
+ return nil unless field&.respond_to?(:type)
241
+
242
+ field.type.size * 8
243
+ end
244
+
245
+ # Raise if total width exceeds parent size
246
+ def validate_total_width(parent_name, widths)
247
+ size = parent_size_bits(parent_name)
248
+ return unless size
249
+
250
+ total = widths.sum
251
+ raise ArgumentError, "Bit width #{total} exceeds :#{parent_name} size (#{size} bits)" if total > size
252
+ end
160
253
  end
161
254
  end
162
255
  end
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  module FFI
4
2
  module BitField
5
3
  # InstanceMethods provides methods for reading and writing bit field values.
@@ -42,7 +40,7 @@ module FFI
42
40
  parent_name, start, width = member_value_info(member_name)
43
41
  if parent_name
44
42
  value = get_member_value(parent_name)
45
- (value >> start) & ((1 << width) - 1)
43
+ (value >> start) & max_value_for_width(width)
46
44
  else
47
45
  get_member_value(member_name)
48
46
  end
@@ -76,7 +74,7 @@ module FFI
76
74
  parent_name, start, width = field_info
77
75
 
78
76
  # Calculate max value for this bit width
79
- max_value = (1 << width) - 1
77
+ max_value = max_value_for_width(width)
80
78
 
81
79
  # Handle negative values by bit-flipping
82
80
  if value.negative?
@@ -105,20 +103,46 @@ module FFI
105
103
 
106
104
  # Update the parent field with the new bit field value
107
105
  parent_value = get_member_value(parent_name)
108
- mask = ((1 << width) - 1) << start
109
- new_value = (parent_value & ~mask) | ((value & ((1 << width) - 1)) << start)
106
+ mask = create_bitmask(width, start)
107
+ new_value = (parent_value & ~mask) | ((value & max_value_for_width(width)) << start)
110
108
 
111
109
  set_member_value(parent_name, new_value)
112
110
  end
113
111
 
114
112
  private
115
113
 
114
+ # Calculates the maximum value that can be stored in the given bit width.
115
+ # For example: 3 bits can store 0b000 to 0b111, so max is 7
116
+ #
117
+ # @param [Integer] width The number of bits
118
+ # @return [Integer] The maximum value
119
+ # @example
120
+ # max_value_for_width(3) # => 7 (0b111)
121
+ # max_value_for_width(4) # => 15 (0b1111)
122
+ def max_value_for_width(width)
123
+ (1 << width) - 1
124
+ end
125
+
126
+ # Creates a bitmask for extracting or setting bits at a specific position.
127
+ # For example: width=3, start=2 creates 0b11100 (mask for bits 2,3,4)
128
+ #
129
+ # @param [Integer] width The number of bits in the mask
130
+ # @param [Integer] start The starting bit position (0-indexed from right)
131
+ # @return [Integer] The bitmask
132
+ # @example
133
+ # create_bitmask(3, 2) # => 28 (0b11100)
134
+ # create_bitmask(2, 0) # => 3 (0b11)
135
+ def create_bitmask(width, start)
136
+ max_value_for_width(width) << start
137
+ end
138
+
116
139
  # Gets information about a bit field member.
117
140
  #
118
141
  # @param [Symbol] member_name The name of the bit field
119
142
  # @return [Array, nil] An array containing [parent_name, start_bit, width] or nil if not a bit field
120
143
  def member_value_info(member_name)
121
- self.class.instance_variable_get(:@bit_field_hash_table)[member_name]
144
+ hash_table = self.class.instance_variable_get(:@bit_field_hash_table)
145
+ hash_table&.[](member_name)
122
146
  end
123
147
  end
124
148
  end
@@ -1,8 +1,6 @@
1
- # frozen_string_literal: true
2
-
3
1
  module FFI
4
2
  module BitField
5
3
  # Current version of the ffi-bitfield gem.
6
- VERSION = '0.0.11'
4
+ VERSION = '0.1.0'
7
5
  end
8
6
  end
data/lib/ffi/bit_field.rb CHANGED
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  require_relative 'bit_field/version'
4
2
  require_relative 'bit_struct'
5
3
  require_relative 'managed_bit_struct'
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  require 'ffi'
4
2
  require_relative 'bit_field/version'
5
3
  require_relative 'bit_field/class_methods'
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  require 'ffi'
4
2
  require_relative 'bit_field/version'
5
3
  require_relative 'bit_field/class_methods'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ffi-bitfield
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.11
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - kojix2
@@ -23,7 +23,7 @@ dependencies:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
25
  version: '0'
26
- description: bit fields for Ruby-FFI
26
+ description: Provides bit field support for FFI::Struct and FFI::ManagedStruct
27
27
  email:
28
28
  - 2xijok@gmail.com
29
29
  executables: []
@@ -56,7 +56,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
56
56
  - !ruby/object:Gem::Version
57
57
  version: '0'
58
58
  requirements: []
59
- rubygems_version: 3.7.1
59
+ rubygems_version: 3.6.9
60
60
  specification_version: 4
61
- summary: bit fields for Ruby-FFI
61
+ summary: Bit fields for Ruby-FFI
62
62
  test_files: []