|
|
#
|
|
|
# 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<len(fmt):
|
|
|
num,i = getNum(fmt,i)
|
|
|
cur = fmt[i]
|
|
|
try:
|
|
|
format = formatdef[cur]
|
|
|
except KeyError:
|
|
|
raise StructError("%s is not a valid format" % cur)
|
|
|
if num != None :
|
|
|
result += num*format['size']
|
|
|
else:
|
|
|
# if formatdef is native, alignment is native, so we count a
|
|
|
# number of padding bytes until result is a multiple of size
|
|
|
if alignment:
|
|
|
result += format['size'] - result % format['size']
|
|
|
result += format['size']
|
|
|
num = 0
|
|
|
i += 1
|
|
|
return result
|
|
|
|
|
|
def pack(fmt,*args):
|
|
|
"""pack(fmt, v1, v2, ...) -> 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<len(fmt):
|
|
|
num,i = getNum(fmt,i)
|
|
|
cur = fmt[i]
|
|
|
try:
|
|
|
format = formatdef[cur]
|
|
|
except KeyError:
|
|
|
raise StructError("%s is not a valid format" % cur)
|
|
|
if num == None :
|
|
|
num_s = 0
|
|
|
num = 1
|
|
|
else:
|
|
|
num_s = num
|
|
|
|
|
|
if cur == 'x':
|
|
|
result += [b'\0'*num]
|
|
|
elif cur == 's':
|
|
|
if isinstance(args[0], bytes):
|
|
|
padding = num - len(args[0])
|
|
|
result += [args[0][:num] + b'\0'*padding]
|
|
|
args.pop(0)
|
|
|
else:
|
|
|
raise StructError("arg for string format not a string")
|
|
|
elif cur == 'p':
|
|
|
if isinstance(args[0], bytes):
|
|
|
padding = num - len(args[0]) - 1
|
|
|
|
|
|
if padding > 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<len(fmt):
|
|
|
num,i=getNum(fmt,i)
|
|
|
cur = fmt[i]
|
|
|
i += 1
|
|
|
try:
|
|
|
format = formatdef[cur]
|
|
|
except KeyError:
|
|
|
raise StructError("%s is not a valid format" % cur)
|
|
|
|
|
|
if not num :
|
|
|
num = 1
|
|
|
|
|
|
if cur == 'x':
|
|
|
j += num
|
|
|
elif cur == 's':
|
|
|
result.append(data[j:j+num])
|
|
|
j += num
|
|
|
elif cur == 'p':
|
|
|
n=data[j]
|
|
|
if n >= 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'))
|
|
|
|
|
|
|