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