17842e56b97ce677b83bdab09cda48bc2d89ac75aJust"""sstruct.py -- SuperStruct 27842e56b97ce677b83bdab09cda48bc2d89ac75aJust 37842e56b97ce677b83bdab09cda48bc2d89ac75aJustHigher level layer on top of the struct module, enabling to 47842e56b97ce677b83bdab09cda48bc2d89ac75aJustbind names to struct elements. The interface is similar to 57842e56b97ce677b83bdab09cda48bc2d89ac75aJuststruct, except the objects passed and returned are not tuples 67842e56b97ce677b83bdab09cda48bc2d89ac75aJust(or argument lists), but dictionaries or instances. 77842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8153ec402094adbea673e914385b87f1d99191d0bBehdad EsfahbodJust like struct, we use fmt strings to describe a data 97842e56b97ce677b83bdab09cda48bc2d89ac75aJuststructure, except we use one line per element. Lines are 107842e56b97ce677b83bdab09cda48bc2d89ac75aJustseparated by newlines or semi-colons. Each line contains 117842e56b97ce677b83bdab09cda48bc2d89ac75aJusteither one of the special struct characters ('@', '=', '<', 127842e56b97ce677b83bdab09cda48bc2d89ac75aJust'>' or '!') or a 'name:formatchar' combo (eg. 'myFloat:f'). 137842e56b97ce677b83bdab09cda48bc2d89ac75aJustRepetitions, like the struct module offers them are not useful 147842e56b97ce677b83bdab09cda48bc2d89ac75aJustin this context, except for fixed length strings (eg. 'myInt:5h' 15153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahbodis not allowed but 'myString:5s' is). The 'x' fmt character 167842e56b97ce677b83bdab09cda48bc2d89ac75aJust(pad byte) is treated as 'special', since it is by definition 177842e56b97ce677b83bdab09cda48bc2d89ac75aJustanonymous. Extra whitespace is allowed everywhere. 187842e56b97ce677b83bdab09cda48bc2d89ac75aJust 197842e56b97ce677b83bdab09cda48bc2d89ac75aJustThe sstruct module offers one feature that the "normal" struct 207842e56b97ce677b83bdab09cda48bc2d89ac75aJustmodule doesn't: support for fixed point numbers. These are spelled 217842e56b97ce677b83bdab09cda48bc2d89ac75aJustas "n.mF", where n is the number of bits before the point, and m 227842e56b97ce677b83bdab09cda48bc2d89ac75aJustthe number of bits after the point. Fixed point numbers get 237842e56b97ce677b83bdab09cda48bc2d89ac75aJustconverted to floats. 247842e56b97ce677b83bdab09cda48bc2d89ac75aJust 25153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahbodpack(fmt, object): 267842e56b97ce677b83bdab09cda48bc2d89ac75aJust 'object' is either a dictionary or an instance (or actually 277842e56b97ce677b83bdab09cda48bc2d89ac75aJust anything that has a __dict__ attribute). If it is a dictionary, 287842e56b97ce677b83bdab09cda48bc2d89ac75aJust its keys are used for names. If it is an instance, it's 297842e56b97ce677b83bdab09cda48bc2d89ac75aJust attributes are used to grab struct elements from. Returns 307842e56b97ce677b83bdab09cda48bc2d89ac75aJust a string containing the data. 317842e56b97ce677b83bdab09cda48bc2d89ac75aJust 32153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahbodunpack(fmt, data, object=None) 337842e56b97ce677b83bdab09cda48bc2d89ac75aJust If 'object' is omitted (or None), a new dictionary will be 347842e56b97ce677b83bdab09cda48bc2d89ac75aJust returned. If 'object' is a dictionary, it will be used to add 357842e56b97ce677b83bdab09cda48bc2d89ac75aJust struct elements to. If it is an instance (or in fact anything 367842e56b97ce677b83bdab09cda48bc2d89ac75aJust that has a __dict__ attribute), an attribute will be added for 377842e56b97ce677b83bdab09cda48bc2d89ac75aJust each struct element. In the latter two cases, 'object' itself 387842e56b97ce677b83bdab09cda48bc2d89ac75aJust is returned. 397842e56b97ce677b83bdab09cda48bc2d89ac75aJust 40153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahbodunpack2(fmt, data, object=None) 417842e56b97ce677b83bdab09cda48bc2d89ac75aJust Convenience function. Same as unpack, except data may be longer 427842e56b97ce677b83bdab09cda48bc2d89ac75aJust than needed. The returned value is a tuple: (object, leftoverdata). 437842e56b97ce677b83bdab09cda48bc2d89ac75aJust 44153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahbodcalcsize(fmt) 45153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahbod like struct.calcsize(), but uses our own fmt strings: 467842e56b97ce677b83bdab09cda48bc2d89ac75aJust it returns the size of the data in bytes. 477842e56b97ce677b83bdab09cda48bc2d89ac75aJust""" 487842e56b97ce677b83bdab09cda48bc2d89ac75aJust 491ae29591efbb29492ce05378909ccf4028d7c1eeBehdad Esfahbodfrom __future__ import print_function, division, absolute_import 5030e691edd056ba22fa8970280e986747817bec3dBehdad Esfahbodfrom fontTools.misc.py23 import * 513fa26d783f6a2ab5103df66a99d0322491e1d8a6Behdad Esfahbodfrom fontTools.misc.fixedTools import fixedToFloat as fi2fl, floatToFixed as fl2fi 5230e691edd056ba22fa8970280e986747817bec3dBehdad Esfahbodimport struct 5330e691edd056ba22fa8970280e986747817bec3dBehdad Esfahbodimport re 547842e56b97ce677b83bdab09cda48bc2d89ac75aJust 557842e56b97ce677b83bdab09cda48bc2d89ac75aJust__version__ = "1.2" 567842e56b97ce677b83bdab09cda48bc2d89ac75aJust__copyright__ = "Copyright 1998, Just van Rossum <just@letterror.com>" 577842e56b97ce677b83bdab09cda48bc2d89ac75aJust 587842e56b97ce677b83bdab09cda48bc2d89ac75aJust 591fcd04db70a1ead14b57371e1b3a182cfb981e79Behdad Esfahbodclass Error(Exception): 601fcd04db70a1ead14b57371e1b3a182cfb981e79Behdad Esfahbod pass 617842e56b97ce677b83bdab09cda48bc2d89ac75aJust 62153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahboddef pack(fmt, obj): 63153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahbod formatstring, names, fixes = getformat(fmt) 647842e56b97ce677b83bdab09cda48bc2d89ac75aJust elements = [] 65153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahbod if not isinstance(obj, dict): 66153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahbod obj = obj.__dict__ 677842e56b97ce677b83bdab09cda48bc2d89ac75aJust for name in names: 68153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahbod value = obj[name] 69bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod if name in fixes: 707842e56b97ce677b83bdab09cda48bc2d89ac75aJust # fixed point conversion 713fa26d783f6a2ab5103df66a99d0322491e1d8a6Behdad Esfahbod value = fl2fi(value, fixes[name]) 72faaca764a1cd62d376f783df20cd1edce21ca816Behdad Esfahbod elif isinstance(value, basestring): 73a0cd41ddf0277054084e453410b8b1c60922e664Behdad Esfahbod value = tobytes(value) 747842e56b97ce677b83bdab09cda48bc2d89ac75aJust elements.append(value) 7566214cbe8c220625e61a85f386756c6de4ec82b2Behdad Esfahbod data = struct.pack(*(formatstring,) + tuple(elements)) 767842e56b97ce677b83bdab09cda48bc2d89ac75aJust return data 777842e56b97ce677b83bdab09cda48bc2d89ac75aJust 78153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahboddef unpack(fmt, data, obj=None): 79153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahbod if obj is None: 80153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahbod obj = {} 81a0cd41ddf0277054084e453410b8b1c60922e664Behdad Esfahbod data = tobytes(data) 82153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahbod formatstring, names, fixes = getformat(fmt) 83153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahbod if isinstance(obj, dict): 84153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahbod d = obj 857842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 86153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahbod d = obj.__dict__ 877842e56b97ce677b83bdab09cda48bc2d89ac75aJust elements = struct.unpack(formatstring, data) 887842e56b97ce677b83bdab09cda48bc2d89ac75aJust for i in range(len(names)): 897842e56b97ce677b83bdab09cda48bc2d89ac75aJust name = names[i] 907842e56b97ce677b83bdab09cda48bc2d89ac75aJust value = elements[i] 91bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod if name in fixes: 927842e56b97ce677b83bdab09cda48bc2d89ac75aJust # fixed point conversion 933fa26d783f6a2ab5103df66a99d0322491e1d8a6Behdad Esfahbod value = fi2fl(value, fixes[name]) 94a0cd41ddf0277054084e453410b8b1c60922e664Behdad Esfahbod elif isinstance(value, bytes): 95a0cd41ddf0277054084e453410b8b1c60922e664Behdad Esfahbod try: 96a0cd41ddf0277054084e453410b8b1c60922e664Behdad Esfahbod value = tostr(value) 97a0cd41ddf0277054084e453410b8b1c60922e664Behdad Esfahbod except UnicodeDecodeError: 98a0cd41ddf0277054084e453410b8b1c60922e664Behdad Esfahbod pass 99002c32fd0d869e280783777ec57916a9267aaea5Behdad Esfahbod d[name] = value 100153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahbod return obj 1017842e56b97ce677b83bdab09cda48bc2d89ac75aJust 102153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahboddef unpack2(fmt, data, obj=None): 103153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahbod length = calcsize(fmt) 104153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahbod return unpack(fmt, data[:length], obj), data[length:] 1057842e56b97ce677b83bdab09cda48bc2d89ac75aJust 106153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahboddef calcsize(fmt): 107153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahbod formatstring, names, fixes = getformat(fmt) 1087842e56b97ce677b83bdab09cda48bc2d89ac75aJust return struct.calcsize(formatstring) 1097842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1107842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1117842e56b97ce677b83bdab09cda48bc2d89ac75aJust# matches "name:formatchar" (whitespace is allowed) 1127842e56b97ce677b83bdab09cda48bc2d89ac75aJust_elementRE = re.compile( 1137842e56b97ce677b83bdab09cda48bc2d89ac75aJust "\s*" # whitespace 1147842e56b97ce677b83bdab09cda48bc2d89ac75aJust "([A-Za-z_][A-Za-z_0-9]*)" # name (python identifier) 1157842e56b97ce677b83bdab09cda48bc2d89ac75aJust "\s*:\s*" # whitespace : whitespace 116b8e1afa809d19202c1f1e405aa4c7312625947d1Behdad Esfahbod "([cbBhHiIlLqQfd]|[0-9]+[ps]|" # formatchar... 1177842e56b97ce677b83bdab09cda48bc2d89ac75aJust "([0-9]+)\.([0-9]+)(F))" # ...formatchar 1187842e56b97ce677b83bdab09cda48bc2d89ac75aJust "\s*" # whitespace 1197842e56b97ce677b83bdab09cda48bc2d89ac75aJust "(#.*)?$" # [comment] + end of string 1207842e56b97ce677b83bdab09cda48bc2d89ac75aJust ) 1217842e56b97ce677b83bdab09cda48bc2d89ac75aJust 122153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahbod# matches the special struct fmt chars and 'x' (pad byte) 1237842e56b97ce677b83bdab09cda48bc2d89ac75aJust_extraRE = re.compile("\s*([x@=<>!])\s*(#.*)?$") 1247842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1257842e56b97ce677b83bdab09cda48bc2d89ac75aJust# matches an "empty" string, possibly containing whitespace and/or a comment 1267842e56b97ce677b83bdab09cda48bc2d89ac75aJust_emptyRE = re.compile("\s*(#.*)?$") 1277842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1287842e56b97ce677b83bdab09cda48bc2d89ac75aJust_fixedpointmappings = { 1297842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8: "b", 1307842e56b97ce677b83bdab09cda48bc2d89ac75aJust 16: "h", 1317842e56b97ce677b83bdab09cda48bc2d89ac75aJust 32: "l"} 1327842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1337842e56b97ce677b83bdab09cda48bc2d89ac75aJust_formatcache = {} 1347842e56b97ce677b83bdab09cda48bc2d89ac75aJust 135153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahboddef getformat(fmt): 1367842e56b97ce677b83bdab09cda48bc2d89ac75aJust try: 137153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahbod formatstring, names, fixes = _formatcache[fmt] 1387842e56b97ce677b83bdab09cda48bc2d89ac75aJust except KeyError: 139153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahbod lines = re.split("[\n;]", fmt) 1407842e56b97ce677b83bdab09cda48bc2d89ac75aJust formatstring = "" 1417842e56b97ce677b83bdab09cda48bc2d89ac75aJust names = [] 1427842e56b97ce677b83bdab09cda48bc2d89ac75aJust fixes = {} 1437842e56b97ce677b83bdab09cda48bc2d89ac75aJust for line in lines: 1447842e56b97ce677b83bdab09cda48bc2d89ac75aJust if _emptyRE.match(line): 1457842e56b97ce677b83bdab09cda48bc2d89ac75aJust continue 1467842e56b97ce677b83bdab09cda48bc2d89ac75aJust m = _extraRE.match(line) 1477842e56b97ce677b83bdab09cda48bc2d89ac75aJust if m: 1487842e56b97ce677b83bdab09cda48bc2d89ac75aJust formatchar = m.group(1) 149180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if formatchar != 'x' and formatstring: 150153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahbod raise Error("a special fmt char must be first") 1517842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 1527842e56b97ce677b83bdab09cda48bc2d89ac75aJust m = _elementRE.match(line) 1537842e56b97ce677b83bdab09cda48bc2d89ac75aJust if not m: 154153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahbod raise Error("syntax error in fmt: '%s'" % line) 1557842e56b97ce677b83bdab09cda48bc2d89ac75aJust name = m.group(1) 1567842e56b97ce677b83bdab09cda48bc2d89ac75aJust names.append(name) 1577842e56b97ce677b83bdab09cda48bc2d89ac75aJust formatchar = m.group(2) 1587842e56b97ce677b83bdab09cda48bc2d89ac75aJust if m.group(3): 1597842e56b97ce677b83bdab09cda48bc2d89ac75aJust # fixed point 1607842e56b97ce677b83bdab09cda48bc2d89ac75aJust before = int(m.group(3)) 1617842e56b97ce677b83bdab09cda48bc2d89ac75aJust after = int(m.group(4)) 1627842e56b97ce677b83bdab09cda48bc2d89ac75aJust bits = before + after 1637842e56b97ce677b83bdab09cda48bc2d89ac75aJust if bits not in [8, 16, 32]: 1641fcd04db70a1ead14b57371e1b3a182cfb981e79Behdad Esfahbod raise Error("fixed point must be 8, 16 or 32 bits long") 1657842e56b97ce677b83bdab09cda48bc2d89ac75aJust formatchar = _fixedpointmappings[bits] 1667842e56b97ce677b83bdab09cda48bc2d89ac75aJust assert m.group(5) == "F" 1673fa26d783f6a2ab5103df66a99d0322491e1d8a6Behdad Esfahbod fixes[name] = after 1687842e56b97ce677b83bdab09cda48bc2d89ac75aJust formatstring = formatstring + formatchar 169153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahbod _formatcache[fmt] = formatstring, names, fixes 1707842e56b97ce677b83bdab09cda48bc2d89ac75aJust return formatstring, names, fixes 1717842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1727842e56b97ce677b83bdab09cda48bc2d89ac75aJustdef _test(): 173153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahbod fmt = """ 1747842e56b97ce677b83bdab09cda48bc2d89ac75aJust # comments are allowed 1757842e56b97ce677b83bdab09cda48bc2d89ac75aJust > # big endian (see documentation for struct) 1767842e56b97ce677b83bdab09cda48bc2d89ac75aJust # empty lines are allowed: 1777842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1787842e56b97ce677b83bdab09cda48bc2d89ac75aJust ashort: h 1797842e56b97ce677b83bdab09cda48bc2d89ac75aJust along: l 1807842e56b97ce677b83bdab09cda48bc2d89ac75aJust abyte: b # a byte 1817842e56b97ce677b83bdab09cda48bc2d89ac75aJust achar: c 1827842e56b97ce677b83bdab09cda48bc2d89ac75aJust astr: 5s 1837842e56b97ce677b83bdab09cda48bc2d89ac75aJust afloat: f; adouble: d # multiple "statements" are allowed 1847842e56b97ce677b83bdab09cda48bc2d89ac75aJust afixed: 16.16F 1857842e56b97ce677b83bdab09cda48bc2d89ac75aJust """ 1867842e56b97ce677b83bdab09cda48bc2d89ac75aJust 187153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahbod print('size:', calcsize(fmt)) 1887842e56b97ce677b83bdab09cda48bc2d89ac75aJust 189e388db566b9ba42669c7e353db4293cf27bc2a5bBehdad Esfahbod class foo(object): 1907842e56b97ce677b83bdab09cda48bc2d89ac75aJust pass 1917842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1927842e56b97ce677b83bdab09cda48bc2d89ac75aJust i = foo() 1937842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1947842e56b97ce677b83bdab09cda48bc2d89ac75aJust i.ashort = 0x7fff 1957842e56b97ce677b83bdab09cda48bc2d89ac75aJust i.along = 0x7fffffff 1967842e56b97ce677b83bdab09cda48bc2d89ac75aJust i.abyte = 0x7f 1977842e56b97ce677b83bdab09cda48bc2d89ac75aJust i.achar = "a" 1987842e56b97ce677b83bdab09cda48bc2d89ac75aJust i.astr = "12345" 1997842e56b97ce677b83bdab09cda48bc2d89ac75aJust i.afloat = 0.5 2007842e56b97ce677b83bdab09cda48bc2d89ac75aJust i.adouble = 0.5 2017842e56b97ce677b83bdab09cda48bc2d89ac75aJust i.afixed = 1.5 2027842e56b97ce677b83bdab09cda48bc2d89ac75aJust 203153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahbod data = pack(fmt, i) 2043ec6a258238b6068e4eef3fe579f1f5c0a06bbbaBehdad Esfahbod print('data:', repr(data)) 205153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahbod print(unpack(fmt, data)) 2067842e56b97ce677b83bdab09cda48bc2d89ac75aJust i2 = foo() 207153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahbod unpack(fmt, data, i2) 2083ec6a258238b6068e4eef3fe579f1f5c0a06bbbaBehdad Esfahbod print(vars(i2)) 2097842e56b97ce677b83bdab09cda48bc2d89ac75aJust 2107842e56b97ce677b83bdab09cda48bc2d89ac75aJustif __name__ == "__main__": 2117842e56b97ce677b83bdab09cda48bc2d89ac75aJust _test() 212