lib/bit_fields.rb in elia-1.2.0 vs lib/bit_fields.rb in elia-2.3.2
- old
+ new
@@ -1,6 +1,7 @@
require 'world_logger'
+require 'ruby19' # for string ecoding compatibility
#
# BitFields provides a simple way to extract a values from bit fields,
# especially if they don't correspond to standard sizes (such as +char+, +int+,
# +long+, etc., see <tt>String#unpack</tt> for further informations).
@@ -68,20 +69,27 @@
attr_reader :bit_fields
# Collects the full <tt>String#unpack</tt> directive used to parse the raw value.
attr_reader :unpack_recipe
+ # Defines accessors for the attributes hash
+ def bits_attr_accessor name
+ class_eval "def #{name}; self.attributes[#{name.inspect}]; end;", __FILE__, __LINE__
+ class_eval "def #{name}=(val); self.attributes[#{name.inspect}]=val; end;", __FILE__, __LINE__
+ end
##
# Defines a field to be extracted with String#unpack from the raw value
#
# +name+ :: the name of the field (that will be used to access it)
# +unpack_recipe+ :: the <tt>String#unpack</tt> directive corresponding to this field (optional, defaults to char: "C")
# +bit_fields_definitions_block+ :: the block in which +bit_fields+ can be defined (optional)
#
# Also defines the attribute reader method
#
+ # TODO: add a skip bits syntax
+ #
def field name, unpack_recipe = 'C', &bit_fields_definitions_block
include InstanceMethods # when used we include instance methods
# Setup class "instance" vars
@fields ||= []
@@ -90,38 +98,41 @@
# Register the field definition
@unpack_recipe << unpack_recipe
@fields << name
- # Define the attribute reader
- class_eval "def #{name}; self.attributes[#{name.inspect}]; end;", __FILE__, __LINE__
+ # Define the attribute accessor
+ bits_attr_accessor(name)
# There's a bit-structure too?
if block_given?
@_current_bit_fields = []
bit_fields_definitions_block.call
- @bit_fields[name] = @_current_bit_fields.reverse
+ @bit_fields[name] = @_current_bit_fields
@_current_bit_fields = nil
end
end
##
# Defines a <em>bit field</em> to be extracted from a +field+
#
# +name+ :: the name of the bit field (that will be used to access it)
# +width+ :: the number of bits from which this value should be extracted
#
+ # TODO: add options to enable method aliases
+ # TODO: add a skip bits syntax
+ #
def bit_field name, width
raise "'bit_field' can be used only inside a 'field' block." if @_current_bit_fields.nil?
# Register the bit field definition
@_current_bit_fields << [name, width, bit_mask(width)]
- # Define the attribute reader
- class_eval "def #{name}; self.attributes[#{name.inspect}]; end\n", __FILE__, __LINE__
+ # Define the attribute accessor
+ bits_attr_accessor(name)
if width == 1 or name.to_s =~ /_flag$/
# Define a question mark method if the size is 1 bit
class_eval "def #{name}?; self.attributes[#{name.inspect}] != 0; end\n", __FILE__, __LINE__
@@ -149,35 +160,30 @@
attr_reader :attributes
# caches the bin string unpacked values
attr_reader :unpacked
+ class ValueOverflow < StandardError
+ end
+
# Takes the raw binary string and parses it
- def initialize bit_string
- parse_bit_fields(bit_string.dup.freeze)
+ def initialize bit_string_or_hash
+ if bit_string_or_hash.kind_of?(Hash)
+ @attributes = bit_string_or_hash
+ pack_bit_fields
+ else
+ parse_bit_fields(bit_string_or_hash) #.dup.freeze REMOVED: seems useless
+ end
end
# Makes defined fields accessible like a +Hash+
def [](name)
self.attributes[name]
end
private
- def eat_right_bits original_value, bits_number, bit_mask
- # Filter the original value with the
- # proper bitmask to get the rightmost bits
- new_value = original_value & bit_mask
-
- # Eat those rightmost bits
- # wich we have just consumed
- remaning = original_value >> bits_number
-
- # Return also the remaning bits
- return new_value, remaning
- end
-
# Parses the raw value extracting the defined bit fields
def parse_bit_fields raw
@raw = raw
# Setup
@@ -186,24 +192,58 @@
self.class.fields.each_with_index do |name, position|
@attributes[name] = @unpacked[position]
- # We must extract bits from end since
- # ruby doesn't have types (and fixed lengths)
if bit_fields = self.class.bit_fields[name]
-
+
bit_value = attributes[name]
+
+ # We must extract bits from end since
+ # ruby doesn't have types (and fixed lengths)
+ bit_fields.reverse.each do |(bit_name, bits_number, bit_mask)|
+ @attributes[bit_name] = bit_value & bit_mask
+ bit_value = bit_value >> bits_number
+ end
+ end
+ end
+ end
+
+ public
+
+
+ # Parses the raw value extracting the defined bit fields
+ def pack_bit_fields
+ @unpacked = []
+
+ self.class.fields.each_with_index do |name, position|
+
+ if bit_fields = self.class.bit_fields[name]
+
+ bit_value = 0
bit_fields.each do |(bit_name, bits_number, bit_mask)|
- # @attributes[bit_name], bit_value = eat_right_bits(bit_value, bits_number, bit_mask)
- # logger.debug "#{bit_name.to_s.rjust(20)}: #{bit_value.to_s(2).rjust(40)} & #{bit_mask.to_s(2).rjust(20)} = #{(bit_value & bit_mask).to_s(2).rjust(20)}"
+ masked = @attributes[bit_name] & bit_mask
- @attributes[bit_name] = bit_value & bit_mask
- bit_value = bit_value >> bits_number
+ raise ValueOverflow,
+ "the value #{@attributes[bit_name]} "+
+ "is too big for #{bits_number} bits" if masked != @attributes[bit_name]
+
+ bit_value = bit_value << bits_number
+ bit_value |= masked
end
+
+ # Value of fields composed by binary fields is always overwritten
+ # by the composition of the latter
+ attributes[name] = bit_value
end
+
+ @unpacked[position] = @attributes[name] || 0
+
end
+
+ @raw = @unpacked.pack( self.class.unpack_recipe )
end
+ alias pack pack_bit_fields
def to_s
raw.to_s
end