"""Process raw qstr file and output qstr data with length, hash and data bytes.This script works with Python 2.7, 3.3 and 3.4.For documentation about the format of compressed translated strings, seesupervisor/shared/translate.h"""from __future__ import print_functionimport reimport sysimport collectionsimport gettextimport os.pathpy = os.path.dirname(sys.argv[0])top = os.path.dirname(py)sys.path.append(os.path.join(top, "tools/huffman"))import huffman# Python 2/3 compatibility:# - iterating through bytes is different# - codepoint2name lives in a different moduleimport platformif platform.python_version_tuple()[0] == '2':bytes_cons = lambda val, enc=None: bytearray(val)from htmlentitydefs import codepoint2nameelif platform.python_version_tuple()[0] == '3':bytes_cons = bytesfrom html.entities import codepoint2name# end compatibility codecodepoint2name[ord('-')] = 'hyphen';# add some custom names to map characters that aren't in HTMLcodepoint2name[ord(' ')] = 'space'codepoint2name[ord('\'')] = 'squot'codepoint2name[ord(',')] = 'comma'codepoint2name[ord('.')] = 'dot'codepoint2name[ord(':')] = 'colon'codepoint2name[ord(';')] = 'semicolon'codepoint2name[ord('/')] = 'slash'codepoint2name[ord('%')] = 'percent'codepoint2name[ord('#')] = 'hash'codepoint2name[ord('(')] = 'paren_open'codepoint2name[ord(')')] = 'paren_close'codepoint2name[ord('[')] = 'bracket_open'codepoint2name[ord(']')] = 'bracket_close'codepoint2name[ord('{')] = 'brace_open'codepoint2name[ord('}')] = 'brace_close'codepoint2name[ord('*')] = 'star'codepoint2name[ord('!')] = 'bang'codepoint2name[ord('\\')] = 'backslash'codepoint2name[ord('+')] = 'plus'codepoint2name[ord('$')] = 'dollar'codepoint2name[ord('=')] = 'equals'codepoint2name[ord('?')] = 'question'codepoint2name[ord('@')] = 'at_sign'codepoint2name[ord('^')] = 'caret'codepoint2name[ord('|')] = 'pipe'codepoint2name[ord('~')] = 'tilde'C_ESCAPES = {"\a": "\\a","\b": "\\b","\f": "\\f","\n": "\\n","\r": "\\r","\t": "\\t","\v": "\\v","\'": "\\'","\"": "\\\""}# this must match the equivalent function in qstr.cdef compute_hash(qstr, bytes_hash):hash = 5381for b in qstr:hash = (hash * 33) ^ b# Make sure that valid hash is never zero, zero means "hash not computed"return (hash & ((1 << (8 * bytes_hash)) - 1)) or 1def translate(translation_file, i18ns):with open(translation_file, "rb") as f:table = gettext.GNUTranslations(f)translations = []for original in i18ns:unescaped = originalfor s in C_ESCAPES:unescaped = unescaped.replace(C_ESCAPES[s], s)translation = table.gettext(unescaped)# Add in carriage returns to work in terminalstranslation = translation.replace("\n", "\r\n")translations.append((original, translation))return translationsdef compute_huffman_coding(translations, qstrs, compression_filename):all_strings = [x[1] for x in translations]# go through each qstr and print it outfor _, _, qstr in qstrs.values():all_strings.append(qstr)all_strings_concat = "".join(all_strings)counts = collections.Counter(all_strings_concat)cb = huffman.codebook(counts.items())values = []length_count = {}renumbered = 0last_l = Nonecanonical = {}for ch, code in sorted(cb.items(), key=lambda x: (len(x[1]), x[0])):values.append(ch)l = len(code)if l not in length_count:length_count[l] = 0length_count[l] += 1if last_l:renumbered <<= (l - last_l)canonical[ch] = '{0:0{width}b}'.format(renumbered, width=l)s = C_ESCAPES.get(ch, ch)print("//", ord(ch), s, counts[ch], canonical[ch], renumbered)renumbered += 1last_l = llengths = bytearray()print("// length count", length_count)for i in range(1, max(length_count) + 2):lengths.append(length_count.get(i, 0))print("// values", values, "lengths", len(lengths), lengths)print("// estimated total memory size", len(lengths) + 2*len(values) + sum(len(cb[u]) for u in all_strings_concat))print("//", values, lengths)values_type = "uint16_t" if max(ord(u) for u in values) > 255 else "uint8_t"max_translation_encoded_length = max(len(translation.encode("utf-8")) for original,translation in translations)with open(compression_filename, "w") as f:f.write("const uint8_t lengths[] = {{ {} }};\n".format(", ".join(map(str, lengths))))f.write("const {} values[] = {{ {} }};\n".format(values_type, ", ".join(str(ord(u)) for u in values)))f.write("#define compress_max_length_bits ({})\n".format(max_translation_encoded_length.bit_length()))return values, lengthsdef decompress(encoding_table, encoded, encoded_length_bits):values, lengths = encoding_tabledec = []this_byte = 0this_bit = 7b = encoded[this_byte]bits = 0for i in range(encoded_length_bits):bits <<= 1if 0x80 & b:bits |= 1b <<= 1if this_bit == 0:this_bit = 7this_byte += 1if this_byte < len(encoded):b = encoded[this_byte]else:this_bit -= 1length = bitsi = 0while i < length:bits = 0bit_length = 0max_code = lengths[0]searched_length = lengths[0]while True:bits <<= 1if 0x80 & b:bits |= 1b <<= 1bit_length += 1if this_bit == 0:this_bit = 7this_byte += 1if this_byte < len(encoded):b = encoded[this_byte]else:this_bit -= 1if max_code > 0 and bits < max_code:#print('{0:0{width}b}'.format(bits, width=bit_length))breakmax_code = (max_code << 1) + lengths[bit_length]searched_length += lengths[bit_length]v = values[searched_length + bits - max_code]i += len(v.encode('utf-8'))dec.append(v)return ''.join(dec)def compress(encoding_table, decompressed, encoded_length_bits, len_translation_encoded):if not isinstance(decompressed, str):raise TypeError()values, lengths = encoding_tableenc = bytearray(len(decompressed) * 3)#print(decompressed)#print(lengths)current_bit = 7current_byte = 0code = len_translation_encodedbits = encoded_length_bits+1for i in range(bits - 1, 0, -1):if len_translation_encoded & (1 << (i - 1)):enc[current_byte] |= 1 << current_bitif current_bit == 0:current_bit = 7#print("packed {0:0{width}b}".format(enc[current_byte], width=8))current_byte += 1else:current_bit -= 1for c in decompressed:#print()#print("char", c, values.index(c))start = 0end = lengths[0]bits = 1compressed = Nonecode = 0while compressed is None:s = starte = end#print("{0:0{width}b}".format(code, width=bits))# Binary search!while e > s:midpoint = (s + e) // 2#print(s, e, midpoint)if values[midpoint] == c:compressed = code + (midpoint - start)#print("found {0:0{width}b}".format(compressed, width=bits))breakelif c < values[midpoint]:e = midpointelse:s = midpoint + 1code += end - startcode <<= 1start = endend += lengths[bits]bits += 1#print("next bit", bits)for i in range(bits - 1, 0, -1):if compressed & (1 << (i - 1)):enc[current_byte] |= 1 << current_bitif current_bit == 0:current_bit = 7#print("packed {0:0{width}b}".format(enc[current_byte], width=8))current_byte += 1else:current_bit -= 1if current_bit != 7:current_byte += 1if current_byte > len(decompressed):print("Note: compression increased length", repr(decompressed), len(decompressed), current_byte, file=sys.stderr)return enc[:current_byte]def qstr_escape(qst):def esc_char(m):c = ord(m.group(0))try:name = codepoint2name[c]except KeyError:name = '0x%02x' % creturn "_" + name + '_'return re.sub(r'[^A-Za-z0-9_]', esc_char, qst)def parse_input_headers(infiles):# read the qstrs in from the input filesqcfgs = {}qstrs = {}i18ns = set()for infile in infiles:with open(infile, 'rt') as f:for line in f:line = line.strip()# is this a config line?match = re.match(r'^QCFG\((.+), (.+)\)', line)if match:value = match.group(2)if value[0] == '(' and value[-1] == ')':# strip parenthesis from config valuevalue = value[1:-1]qcfgs[match.group(1)] = valuecontinuematch = re.match(r'^TRANSLATE\("(.*)"\)$', line)if match:i18ns.add(match.group(1))continue# is this a QSTR line?match = re.match(r'^Q\((.*)\)$', line)if not match:continue# get the qstr valueqstr = match.group(1)# special case to specify control charactersif qstr == '\\n':qstr = '\n'# work out the corresponding qstr nameident = qstr_escape(qstr)# don't add duplicatesif ident in qstrs:continue# add the qstr to the list, with order number to retain original order in fileorder = len(qstrs)# but put special method names like __add__ at the top of list, so# that their id's fit into a byteif ident == "":# Sort empty qstr above all stillorder = -200000elif ident == "__dir__":# Put __dir__ after empty qstr for builtin dir() to workorder = -190000elif ident.startswith("__"):order -= 100000qstrs[ident] = (order, ident, qstr)if not qcfgs and qstrs:sys.stderr.write("ERROR: Empty preprocessor output - check for errors above\n")sys.exit(1)return qcfgs, qstrs, i18nsdef make_bytes(cfg_bytes_len, cfg_bytes_hash, qstr):qbytes = bytes_cons(qstr, 'utf8')qlen = len(qbytes)qhash = compute_hash(qbytes, cfg_bytes_hash)if all(32 <= ord(c) <= 126 and c != '\\' and c != '"' for c in qstr):# qstr is all printable ASCII so render it as-is (for easier debugging)qdata = qstrelse:# qstr contains non-printable codes so render entire thing as hex pairsqdata = ''.join(('\\x%02x' % b) for b in qbytes)if qlen >= (1 << (8 * cfg_bytes_len)):print('qstr is too long:', qstr)assert Falseqlen_str = ('\\x%02x' * cfg_bytes_len) % tuple(((qlen >> (8 * i)) & 0xff) for i in range(cfg_bytes_len))qhash_str = ('\\x%02x' * cfg_bytes_hash) % tuple(((qhash >> (8 * i)) & 0xff) for i in range(cfg_bytes_hash))return '(const byte*)"%s%s" "%s"' % (qhash_str, qlen_str, qdata)def print_qstr_data(encoding_table, qcfgs, qstrs, i18ns):# get config variablescfg_bytes_len = int(qcfgs['BYTES_IN_LEN'])cfg_bytes_hash = int(qcfgs['BYTES_IN_HASH'])# print out the starter of the generated C header fileprint('// This file was automatically generated by makeqstrdata.py')print('')# add NULL qstr with no hash or dataprint('QDEF(MP_QSTR_NULL, (const byte*)"%s%s" "")' % ('\\x00' * cfg_bytes_hash, '\\x00' * cfg_bytes_len))total_qstr_size = 0total_qstr_compressed_size = 0# go through each qstr and print it outfor order, ident, qstr in sorted(qstrs.values(), key=lambda x: x[0]):qbytes = make_bytes(cfg_bytes_len, cfg_bytes_hash, qstr)print('QDEF(MP_QSTR_%s, %s)' % (ident, qbytes))total_qstr_size += len(qstr)total_text_size = 0total_text_compressed_size = 0max_translation_encoded_length = max(len(translation.encode("utf-8")) for original, translation in i18ns)encoded_length_bits = max_translation_encoded_length.bit_length()for original, translation in i18ns:translation_encoded = translation.encode("utf-8")compressed = compress(encoding_table, translation, encoded_length_bits, len(translation_encoded))total_text_compressed_size += len(compressed)decompressed = decompress(encoding_table, compressed, encoded_length_bits)assert decompressed == translationfor c in C_ESCAPES:decompressed = decompressed.replace(c, C_ESCAPES[c])print("TRANSLATION(\"{}\", {}) // {}".format(original, ", ".join(["{:d}".format(x) for x in compressed]), decompressed))total_text_size += len(translation.encode("utf-8"))print()print("// {} bytes worth of qstr".format(total_qstr_size))print("// {} bytes worth of translations".format(total_text_size))print("// {} bytes worth of translations compressed".format(total_text_compressed_size))print("// {} bytes saved".format(total_text_size - total_text_compressed_size))def print_qstr_enums(qstrs):# print out the starter of the generated C header fileprint('// This file was automatically generated by makeqstrdata.py')print('')# add NULL qstr with no hash or dataprint('QENUM(MP_QSTR_NULL)')# go through each qstr and print it outfor order, ident, qstr in sorted(qstrs.values(), key=lambda x: x[0]):print('QENUM(MP_QSTR_%s)' % (ident,))if __name__ == "__main__":import argparseparser = argparse.ArgumentParser(description='Process QSTR definitions into headers for compilation')parser.add_argument('infiles', metavar='N', type=str, nargs='+',help='an integer for the accumulator')parser.add_argument('--translation', default=None, type=str,help='translations for i18n() items')parser.add_argument('--compression_filename', default=None, type=str,help='header for compression info')args = parser.parse_args()qcfgs, qstrs, i18ns = parse_input_headers(args.infiles)if args.translation:i18ns = sorted(i18ns)translations = translate(args.translation, i18ns)encoding_table = compute_huffman_coding(translations, qstrs, args.compression_filename)print_qstr_data(encoding_table, qcfgs, qstrs, translations)else:print_qstr_enums(qstrs)
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。