# # This module is a pure Python version of pypy.module.struct. # It is only imported if the vastly faster pypy.module.struct is not # compiled in. For now we keep this version for reference and # because pypy.module.struct is not ootype-backend-friendly yet. # # this module 'borrowed' from # https://bitbucket.org/pypy/pypy/src/18626459a9b2/lib_pypy/_struct.py?at=py3k-listview_str # with many bug fixes """Functions to convert between Python values and C structs. Python strings are used to hold the data representing the C struct and also as format strings to describe the layout of data in the C struct. The optional first format char indicates byte order, size and alignment: @: native order, size & alignment (default) =: native order, std. size & alignment <: little-endian, std. size & alignment >: big-endian, std. size & alignment !: same as > The remaining chars indicate types of args and must match exactly; these can be preceded by a decimal repeat count: x: pad byte (no data); c:char; b:signed byte; B:unsigned byte; h:short; H:unsigned short; i:int; I:unsigned int; l:long; L:unsigned long; f:float; d:double. Special cases (preceding decimal count indicates length): s:string (array of char); p: pascal string (with count byte). Special case (only available in native format): P:an integer type that is wide enough to hold a pointer. Special case (not in native mode unless 'long long' in platform C): q:long long; Q:unsigned long long Whitespace between formats is ignored. The variable struct.error is an exception raised on errors.""" import math, sys # TODO: XXX Find a way to get information on native sizes and alignments class StructError(Exception): pass error = StructError def unpack_int(data,index,size,le): bytes = [b for b in data[index:index+size]] if le == 'little': bytes.reverse() number = 0 for b in bytes: number = number << 8 | b return int(number) def unpack_signed_int(data,index,size,le): number = unpack_int(data,index,size,le) max = 2**(size*8) if number > 2**(size*8 - 1) - 1: number = int(-1*(max - number)) return number INFINITY = 1e200 * 1e200 NAN = INFINITY / INFINITY def unpack_char(data,index,size,le): return data[index:index+size] def pack_int(number,size,le): x=number res=[] for i in range(size): res.append(x&0xff) x >>= 8 if le == 'big': res.reverse() return bytes(res) def pack_signed_int(number,size,le): if not isinstance(number, int): raise StructError("argument for i,I,l,L,q,Q,h,H must be integer") if number > 2**(8*size-1)-1 or number < -1*2**(8*size-1): raise OverflowError("Number:%i too large to convert" % number) return pack_int(number,size,le) def pack_unsigned_int(number,size,le): if not isinstance(number, int): raise StructError("argument for i,I,l,L,q,Q,h,H must be integer") if number < 0: raise TypeError("can't convert negative long to unsigned") if number > 2**(8*size)-1: raise OverflowError("Number:%i too large to convert" % number) return pack_int(number,size,le) def pack_char(char,size,le): return bytes(char) def isinf(x): return x != 0.0 and x / 2 == x def isnan(v): return v != v*1.0 or (v == 1.0 and v == 2.0) def pack_float(x, size, le): unsigned = float_pack(x, size) result = [] for i in range(size): result.append((unsigned >> (i * 8)) & 0xFF) if le == "big": result.reverse() return bytes(result) def unpack_float(data, index, size, le): binary = [data[i] for i in range(index, index + size)] if le == "big": binary.reverse() unsigned = 0 for i in range(size): unsigned |= binary[i] << (i * 8) return float_unpack(unsigned, size, le) def round_to_nearest(x): """Python 3 style round: round a float x to the nearest int, but unlike the builtin Python 2.x round function: - return an int, not a float - do round-half-to-even, not round-half-away-from-zero. We assume that x is finite and nonnegative; except wrong results if you use this for negative x. """ int_part = int(x) frac_part = x - int_part if frac_part > 0.5 or frac_part == 0.5 and int_part & 1 == 1: int_part += 1 return int_part def float_unpack(Q, size, le): """Convert a 32-bit or 64-bit integer created by float_pack into a Python float.""" if size == 8: MIN_EXP = -1021 # = sys.float_info.min_exp MAX_EXP = 1024 # = sys.float_info.max_exp MANT_DIG = 53 # = sys.float_info.mant_dig BITS = 64 elif size == 4: MIN_EXP = -125 # C's FLT_MIN_EXP MAX_EXP = 128 # FLT_MAX_EXP MANT_DIG = 24 # FLT_MANT_DIG BITS = 32 else: raise ValueError("invalid size value") if Q >> BITS: raise ValueError("input out of range") # extract pieces sign = Q >> BITS - 1 exp = (Q & ((1 << BITS - 1) - (1 << MANT_DIG - 1))) >> MANT_DIG - 1 mant = Q & ((1 << MANT_DIG - 1) - 1) if exp == MAX_EXP - MIN_EXP + 2: # nan or infinity result = float('nan') if mant else float('inf') elif exp == 0: # subnormal or zero result = math.ldexp(float(mant), MIN_EXP - MANT_DIG) else: # normal mant += 1 << MANT_DIG - 1 result = math.ldexp(float(mant), exp + MIN_EXP - MANT_DIG - 1) return -result if sign else result def float_pack(x, size): """Convert a Python float x into a 64-bit unsigned integer with the same byte representation.""" if size == 8: MIN_EXP = -1021 # = sys.float_info.min_exp MAX_EXP = 1024 # = sys.float_info.max_exp MANT_DIG = 53 # = sys.float_info.mant_dig BITS = 64 elif size == 4: MIN_EXP = -125 # C's FLT_MIN_EXP MAX_EXP = 128 # FLT_MAX_EXP MANT_DIG = 24 # FLT_MANT_DIG BITS = 32 else: raise ValueError("invalid size value") sign = math.copysign(1.0, x) < 0.0 if math.isinf(x): mant = 0 exp = MAX_EXP - MIN_EXP + 2 elif math.isnan(x): mant = 1 << (MANT_DIG-2) # other values possible exp = MAX_EXP - MIN_EXP + 2 elif x == 0.0: mant = 0 exp = 0 else: m, e = math.frexp(abs(x)) # abs(x) == m * 2**e exp = e - (MIN_EXP - 1) if exp > 0: # Normal case. mant = round_to_nearest(m * (1 << MANT_DIG)) mant -= 1 << MANT_DIG - 1 else: # Subnormal case. if exp + MANT_DIG - 1 >= 0: mant = round_to_nearest(m * (1 << exp + MANT_DIG - 1)) else: mant = 0 exp = 0 # Special case: rounding produced a MANT_DIG-bit mantissa. assert 0 <= mant <= 1 << MANT_DIG - 1 if mant == 1 << MANT_DIG - 1: mant = 0 exp += 1 # Raise on overflow (in some circumstances, may want to return # infinity instead). if exp >= MAX_EXP - MIN_EXP + 2: raise OverflowError("float too large to pack in this format") # check constraints assert 0 <= mant < 1 << MANT_DIG - 1 assert 0 <= exp <= MAX_EXP - MIN_EXP + 2 assert 0 <= sign <= 1 return ((sign << BITS - 1) | (exp << MANT_DIG - 1)) | mant big_endian_format = { 'x':{ 'size' : 1, 'alignment' : 0, 'pack' : None, 'unpack' : None}, 'b':{ 'size' : 1, 'alignment' : 0, 'pack' : pack_signed_int, 'unpack' : unpack_signed_int}, 'B':{ 'size' : 1, 'alignment' : 0, 'pack' : pack_unsigned_int, 'unpack' : unpack_int}, 'c':{ 'size' : 1, 'alignment' : 0, 'pack' : pack_char, 'unpack' : unpack_char}, 's':{ 'size' : 1, 'alignment' : 0, 'pack' : None, 'unpack' : None}, 'p':{ 'size' : 1, 'alignment' : 0, 'pack' : None, 'unpack' : None}, 'h':{ 'size' : 2, 'alignment' : 0, 'pack' : pack_signed_int, 'unpack' : unpack_signed_int}, 'H':{ 'size' : 2, 'alignment' : 0, 'pack' : pack_unsigned_int, 'unpack' : unpack_int}, 'i':{ 'size' : 4, 'alignment' : 0, 'pack' : pack_signed_int, 'unpack' : unpack_signed_int}, 'I':{ 'size' : 4, 'alignment' : 0, 'pack' : pack_unsigned_int, 'unpack' : unpack_int}, 'l':{ 'size' : 4, 'alignment' : 0, 'pack' : pack_signed_int, 'unpack' : unpack_signed_int}, 'L':{ 'size' : 4, 'alignment' : 0, 'pack' : pack_unsigned_int, 'unpack' : unpack_int}, 'q':{ 'size' : 8, 'alignment' : 0, 'pack' : pack_signed_int, 'unpack' : unpack_signed_int}, 'Q':{ 'size' : 8, 'alignment' : 0, 'pack' : pack_unsigned_int, 'unpack' : unpack_int}, 'f':{ 'size' : 4, 'alignment' : 0, 'pack' : pack_float, 'unpack' : unpack_float}, 'd':{ 'size' : 8, 'alignment' : 0, 'pack' : pack_float, 'unpack' : unpack_float}, } default = big_endian_format formatmode={ '<' : (default, 'little'), '>' : (default, 'big'), '!' : (default, 'big'), '=' : (default, sys.byteorder), '@' : (default, sys.byteorder) } def getmode(fmt): try: formatdef,endianness = formatmode[fmt[0]] alignment = fmt[0] not in formatmode or fmt[0]=='@' index = 1 except (IndexError, KeyError): formatdef,endianness = formatmode['@'] alignment = True index = 0 return formatdef,endianness,index,alignment def getNum(fmt,i): num=None cur = fmt[i] while ('0'<= cur ) and ( cur <= '9'): if num == None: num = int(cur) else: num = 10*num + int(cur) i += 1 cur = fmt[i] return num,i def calcsize(fmt): """calcsize(fmt) -> int Return size of C struct described by format string fmt. See struct.__doc__ for more on format strings.""" formatdef,endianness,i,alignment = getmode(fmt) num = 0 result = 0 while i string Return string containing values v1, v2, ... packed according to fmt. See struct.__doc__ for more on format strings.""" formatdef,endianness,i,alignment = getmode(fmt) args = list(args) n_args = len(args) result = [] while i 0: result += [bytes([len(args[0])]) + args[0][:num-1] + b'\0'*padding] else: if num<255: result += [bytes([num-1]) + args[0][:num-1]] else: result += [bytes([255]) + args[0][:num-1]] args.pop(0) else: raise StructError("arg for string format not a string") else: if len(args) < num: raise StructError("insufficient arguments to pack") for var in args[:num]: # pad with 0 until position is a multiple of size if len(result) and alignment: padding = format['size'] - len(result) % format['size'] result += [bytes([0])]*padding result += [format['pack'](var,format['size'],endianness)] args=args[num:] num = None i += 1 if len(args) != 0: raise StructError("too many arguments for pack format") return b''.join(result) def unpack(fmt,data): """unpack(fmt, string) -> (v1, v2, ...) Unpack the string, containing packed C structure data, according to fmt. Requires len(string)==calcsize(fmt). See struct.__doc__ for more on format strings.""" formatdef,endianness,i,alignment = getmode(fmt) j = 0 num = 0 result = [] length= calcsize(fmt) if length != len (data): raise StructError("unpack str size does not match format") while i= num: n = num-1 result.append(data[j+1:j+n+1]) j += num else: # skip padding bytes until we get at a multiple of size if j>0 and alignment: padding = format['size'] - j % format['size'] j += padding for n in range(num): result += [format['unpack'](data,j,format['size'],endianness)] j += format['size'] return tuple(result) def pack_into(fmt, buf, offset, *args): data = pack(fmt, *args) buf[offset:offset+len(data)] = data def unpack_from(fmt, buf, offset=0): size = calcsize(fmt) data = buf[offset:offset+size] if len(data) != size: raise error("unpack_from requires a buffer of at least %d bytes" % (size,)) return unpack(fmt, data) def _clearcache(): "Clear the internal cache." # No cache in this implementation if __name__=='__main__': t = pack('Bf',1,2) print(t, len(t)) print(unpack('Bf', t)) print(calcsize('Bf'))