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