1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212
|
require 'thread'
require 'bindata/base_primitive'
module BinData
# Defines a number of classes that contain an integer. The integer
# is defined by endian, signedness and number of bytes.
module Int #:nodoc: all
@@mutex = Mutex.new
class << self
def define_class(name, nbits, endian, signed)
@@mutex.synchronize do
unless BinData.const_defined?(name)
new_class = Class.new(BinData::BasePrimitive)
Int.define_methods(new_class, nbits, endian.to_sym, signed.to_sym)
RegisteredClasses.register(name, new_class)
BinData.const_set(name, new_class)
end
end
BinData.const_get(name)
end
def define_methods(int_class, nbits, endian, signed)
raise "nbits must be divisible by 8" unless (nbits % 8).zero?
int_class.module_eval <<-END
def assign(val)
#{create_clamp_code(nbits, signed)}
super(val)
end
def do_num_bytes
#{nbits / 8}
end
#---------------
private
def sensible_default
0
end
def value_to_binary_string(val)
#{create_clamp_code(nbits, signed)}
#{create_to_binary_s_code(nbits, endian, signed)}
end
def read_and_return_value(io)
#{create_read_code(nbits, endian, signed)}
end
END
end
#-------------
private
def create_clamp_code(nbits, signed)
if signed == :signed
max = "(1 << (#{nbits} - 1)) - 1"
min = "-((#{max}) + 1)"
else
max = "(1 << #{nbits}) - 1"
min = "0"
end
"val = val.clamp(#{min}, #{max})"
end
def create_read_code(nbits, endian, signed)
read_str = create_raw_read_code(nbits, endian, signed)
if need_signed_conversion_code?(nbits, signed)
"val = #{read_str} ; #{create_uint2int_code(nbits)}"
else
read_str
end
end
def create_raw_read_code(nbits, endian, signed)
# special case 8bit integers for speed
if nbits == 8
"io.readbytes(1).ord"
else
unpack_str = create_read_unpack_code(nbits, endian, signed)
assemble_str = create_read_assemble_code(nbits, endian, signed)
"(#{unpack_str} ; #{assemble_str})"
end
end
def create_read_unpack_code(nbits, endian, signed)
nbytes = nbits / 8
pack_directive = pack_directive(nbits, endian, signed)
"ints = io.readbytes(#{nbytes}).unpack('#{pack_directive}')"
end
def create_read_assemble_code(nbits, endian, signed)
nwords = nbits / bits_per_word(nbits)
idx = (0...nwords).to_a
idx.reverse! if endian == :big
parts = (0...nwords).collect do |i|
"(ints.at(#{idx[i]}) << #{bits_per_word(nbits) * i})"
end
parts[0] = parts[0].sub(/ << 0\b/, "") # Remove " << 0" for optimisation
parts.join(" + ")
end
def create_to_binary_s_code(nbits, endian, signed)
# special case 8bit integers for speed
return "(val & 0xff).chr" if nbits == 8
pack_directive = pack_directive(nbits, endian, signed)
words = val_as_packed_words(nbits, endian, signed)
pack_str = "[#{words}].pack('#{pack_directive}')"
if need_signed_conversion_code?(nbits, signed)
"#{create_int2uint_code(nbits)} ; #{pack_str}"
else
pack_str
end
end
def val_as_packed_words(nbits, endian, signed)
nwords = nbits / bits_per_word(nbits)
mask = (1 << bits_per_word(nbits)) - 1
vals = (0...nwords).collect { |i| "val >> #{bits_per_word(nbits) * i}" }
vals[0] = vals[0].sub(/ >> 0\b/, "") # Remove " >> 0" for optimisation
vals.reverse! if (endian == :big)
vals = vals.collect { |val| "#{val} & #{mask}" } # TODO: "& mask" is needed to work around jruby bug. Remove this line when fixed.
vals.join(",")
end
def create_int2uint_code(nbits)
"val &= #{(1 << nbits) - 1}"
end
def create_uint2int_code(nbits)
"(val >= #{1 << (nbits - 1)}) ? val - #{1 << nbits} : val"
end
def bits_per_word(nbits)
(nbits % 64).zero? ? 64 :
(nbits % 32).zero? ? 32 :
(nbits % 16).zero? ? 16 :
8
end
def pack_directive(nbits, endian, signed)
nwords = nbits / bits_per_word(nbits)
directives = { 8 => "C", 16 => "S", 32 => "L", 64 => "Q" }
d = directives[bits_per_word(nbits)]
d += ((endian == :big) ? ">" : "<") unless d == "C"
if signed == :signed && directives.key?(nbits)
(d * nwords).downcase
else
d * nwords
end
end
def need_signed_conversion_code?(nbits, signed)
signed == :signed && ![64, 32, 16].include?(nbits)
end
end
end
# Unsigned 1 byte integer.
class Uint8 < BinData::BasePrimitive
Int.define_methods(self, 8, :little, :unsigned)
end
# Signed 1 byte integer.
class Int8 < BinData::BasePrimitive
Int.define_methods(self, 8, :little, :signed)
end
# Create classes on demand
module IntFactory
def const_missing(name)
mappings = {
/^Uint(\d+)be$/ => [:big, :unsigned],
/^Uint(\d+)le$/ => [:little, :unsigned],
/^Int(\d+)be$/ => [:big, :signed],
/^Int(\d+)le$/ => [:little, :signed],
}
mappings.each_pair do |regex, args|
if regex =~ name.to_s
nbits = $1.to_i
if nbits > 0 && (nbits % 8).zero?
return Int.define_class(name, nbits, *args)
end
end
end
super
end
end
BinData.extend IntFactory
end
|