lib/bindata/int.rb



require 'bindata/single'

module BinData
  # Provides a number of classes that contain an integer.  The integer
  # is defined by endian, signedness and number of bytes.

  module Integer #:nodoc: all
    def self.create_int_methods(klass, nbits, endian)
      max = (1 << (nbits - 1)) - 1
      min = -(max + 1)
      clamp = "val = (val < #{min}) ? #{min} : (val > #{max}) ? #{max} : val"

      int2uint = "val = val & #{(1 << nbits) - 1}"
      uint2int = "val = ((val & #{1 << (nbits - 1)}).zero?) ? " +
                 "val & #{max} : -(((~val) & #{max}) + 1)"

      read = create_read_code(nbits, endian)
      to_s = create_to_s_code(nbits, endian)

      define_methods(klass, nbits / 8, clamp, read, to_s, int2uint, uint2int)
    end

    def self.create_uint_methods(klass, nbits, endian)
      min = 0
      max = (1 << nbits) - 1
      clamp = "val = (val < #{min}) ? #{min} : (val > #{max}) ? #{max} : val"

      read = create_read_code(nbits, endian)
      to_s = create_to_s_code(nbits, endian)

      define_methods(klass, nbits / 8, clamp, read, to_s)
    end

    def self.create_read_code(nbits, endian)
      c16 = (endian == :little) ? 'v' : 'n'
      c32 = (endian == :little) ? 'V' : 'N'
      idx = (0 ... (nbits / 32)).to_a
      idx.reverse! if (endian == :little)

      case nbits
      when   8; "io.readbytes(1).unpack('C').at(0)"
      when  16; "io.readbytes(2).unpack('#{c16}').at(0)"
      when  32; "io.readbytes(4).unpack('#{c32}').at(0)"
      when  64; "(a = io.readbytes(8).unpack('#{c32 * 2}'); " +
                     "(a.at(#{idx[0]}) << 32) + " +
                      "a.at(#{idx[1]}))"
      when 128; "(a = io.readbytes(16).unpack('#{c32 * 4}'); " +
                     "((a.at(#{idx[0]}) << 96) + " +
                      "(a.at(#{idx[1]}) << 64) + " +
                      "(a.at(#{idx[2]}) << 32) + " +
                       "a.at(#{idx[3]})))"
      else
        raise "unknown nbits '#{nbits}'"
      end
    end

    def self.create_to_s_code(nbits, endian)
      c16 = (endian == :little) ? 'v' : 'n'
      c32 = (endian == :little) ? 'V' : 'N'
      vals = (0 ... (nbits / 32)).collect { |i| "(val >> #{32 * i})" }
      vals.reverse! if (endian == :little)

      case nbits
      when   8; "val.chr"
      when  16; "[val].pack('#{c16}')"
      when  32; "[val].pack('#{c32}')"
      when  64; "[#{vals[1]} & 0xffffffff, " +
                 "#{vals[0]} & 0xffffffff].pack('#{c32 * 2}')"
      when 128; "[#{vals[3]} & 0xffffffff, " +
                 "#{vals[2]} & 0xffffffff, " +
                 "#{vals[1]} & 0xffffffff, " +
                 "#{vals[0]} & 0xffffffff].pack('#{c32 * 4}')"
      else
        raise "unknown nbits '#{nbits}'"
      end
    end

    def self.define_methods(klass, nbytes, clamp, read, to_s,
                            int2uint = nil, uint2int = nil)
      # define methods in the given class
      klass.module_eval <<-END
        def value=(val)
          #{clamp}
          super(val)
        end

        def _do_num_bytes(ignored)
          #{nbytes}
        end

        #---------------
        private

        def sensible_default
          0
        end

        def val_to_str(val)
          #{clamp}
          #{int2uint unless int2uint.nil?}
          #{to_s}
        end

        def read_val(io)
          val = #{read}
          #{uint2int unless uint2int.nil?}
        end
      END
    end
  end


  # Unsigned 1 byte integer.
  class Uint8 < BinData::Single
    register(self.name, self)
    Integer.create_uint_methods(self, 8, :little)
  end

  # Unsigned 2 byte little endian integer.
  class Uint16le < BinData::Single
    register(self.name, self)
    Integer.create_uint_methods(self, 16, :little)
  end

  # Unsigned 2 byte big endian integer.
  class Uint16be < BinData::Single
    register(self.name, self)
    Integer.create_uint_methods(self, 16, :big)
  end

  # Unsigned 4 byte little endian integer.
  class Uint32le < BinData::Single
    register(self.name, self)
    Integer.create_uint_methods(self, 32, :little)
  end

  # Unsigned 4 byte big endian integer.
  class Uint32be < BinData::Single
    register(self.name, self)
    Integer.create_uint_methods(self, 32, :big)
  end

  # Unsigned 8 byte little endian integer.
  class Uint64le < BinData::Single
    register(self.name, self)
    Integer.create_uint_methods(self, 64, :little)
  end

  # Unsigned 8 byte big endian integer.
  class Uint64be < BinData::Single
    register(self.name, self)
    Integer.create_uint_methods(self, 64, :big)
  end

  # Unsigned 16 byte little endian integer.
  class Uint128le < BinData::Single
    register(self.name, self)
    Integer.create_uint_methods(self, 128, :little)
  end

  # Unsigned 16 byte big endian integer.
  class Uint128be < BinData::Single
    register(self.name, self)
    Integer.create_uint_methods(self, 128, :big)
  end

  # Signed 1 byte integer.
  class Int8 < BinData::Single
    register(self.name, self)
    Integer.create_int_methods(self, 8, :little)
  end

  # Signed 2 byte little endian integer.
  class Int16le < BinData::Single
    register(self.name, self)
    Integer.create_int_methods(self, 16, :little)
  end

  # Signed 2 byte big endian integer.
  class Int16be < BinData::Single
    register(self.name, self)
    Integer.create_int_methods(self, 16, :big)
  end

  # Signed 4 byte little endian integer.
  class Int32le < BinData::Single
    register(self.name, self)
    Integer.create_int_methods(self, 32, :little)
  end

  # Signed 4 byte big endian integer.
  class Int32be < BinData::Single
    register(self.name, self)
    Integer.create_int_methods(self, 32, :big)
  end

  # Signed 8 byte little endian integer.
  class Int64le < BinData::Single
    register(self.name, self)
    Integer.create_int_methods(self, 64, :little)
  end

  # Signed 8 byte big endian integer.
  class Int64be < BinData::Single
    register(self.name, self)
    Integer.create_int_methods(self, 64, :big)
  end

  # Signed 16 byte little endian integer.
  class Int128le < BinData::Single
    register(self.name, self)
    Integer.create_int_methods(self, 128, :little)
  end

  # Signed 16 byte big endian integer.
  class Int128be < BinData::Single
    register(self.name, self)
    Integer.create_int_methods(self, 128, :big)
  end
end