#!/usr/bin/env python # Character map generator # # Copyright (C) 2012-2018 Nirenjan Krishnan (nirenjan@nirenjan.org) # # SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0 """ Generator script to parse character mapping for the X52/X52 Pro MFD """ import sys import re AUTOGEN_HEADER = """ /* * Autogenerated character map file for Saitek X52 Pro * Generated from %s */ #include "x52_char_map.h" """ class MapTable(object): """ Defines a MapTable entry, with each entry storing the value seen so far, the type of the entry, and the value, if it's a value node. """ # Empty list root = [None] * 256 def __init__(self, value_so_far, map_value=None): self.next_level = [None] * 256 self.value_so_far = value_so_far self.map_value = map_value def output_nodes(self): """ Output the individual nodes """ output_lines = [] output_count = 0 for node in self.next_level: if node is not None: output_lines.extend(node.output_nodes()) output_count += 1 if output_count != 0: struct_header = 'static struct map_entry table_%x[64] = {' % \ self.value_so_far output_lines.append(struct_header) for node_index in range(0, 256): node = self.next_level[node_index] if node is not None: output_lines.append(self.dump_entry_line(0x80, node_index, node.value_so_far, node.map_value)) output_lines.extend(['};', '']) return output_lines @staticmethod def dump_entry_line(offset, node_index, value_so_far, map_value): """ Dump the array entry for the current node """ if map_value is None: node_entry_line = '\t[0x%02x] = { table_%x, TYPE_POINTER, 0 },' % \ (node_index - offset, value_so_far) else: node_entry_line = '\t[0x%02x] = { NULL, TYPE_ENTRY, 0x%02x },' % \ (node_index - offset, map_value) return node_entry_line @classmethod def add_to_table(cls, input_val, map_val): """ Add a map value to the lookup table """ try: uchr = unichr(input_val) except NameError: # Python 3 doesn't have unichr, but chr should work uchr = chr(input_val) utf8_str = uchr.encode('utf-8') # Python2 returns the encoded result as a string, wheras # Python3 returns the result as a bytearray. Converting # the string (or bytearray) into a bytearray ensures that # this can be run in both Python2 and Python3 utf8_vals = [c for c in bytearray(utf8_str)] value_so_far = 0 level = cls.root for index, char in enumerate(utf8_vals): value_so_far = (value_so_far << 8) | char if index < (len(utf8_vals) - 1): node = level[char] if node is None: node = cls(value_so_far) level[char] = node level = level[char].next_level else: node = cls(value_so_far, map_val) level[char] = node @classmethod def output_table_as_list(cls): """ Output the map table as a list of lines """ output_lines = [] for node in cls.root: if node is not None: output_lines.extend(node.output_nodes()) output_lines.append('struct map_entry map_root[256] = {') for node_index in range(0, 256): node = cls.root[node_index] if node is not None: output_lines.append(cls.dump_entry_line(0x0, node_index, node.value_so_far, node.map_value)) output_lines.extend(['};', '']) return output_lines class LineFormatError(ValueError): """ Error class for parser """ def parse_line(data): """ Parse a line containing a mapping descriptor. The mapping descriptor must start with a hexadecimal unicode code point, followed by either a single character, or a hexadecimal integer that corresponds to the map value. """ # Strip off comments data = re.sub(re.compile('#.*$'), '', data) # Strip off leading and trailing whitespace data = data.strip() # If the line is empty, it is a comment line if len(data) == 0: return None, None # Find the code point and the target value try: code_point, target = data.strip().split() except ValueError: # Raised when there are either too many, or not enough values in # the string raise LineFormatError('Invalid descriptor format "%s"' % data) # Convert the string to its equivalent numeric value try: code_point = int(code_point, 0) except ValueError: raise LineFormatError('Invalid code point "%s"' % code_point) # Check if the target is a single character if len(target) == 1: target = ord(target) else: # Try parsing the target as an integer try: target = int(target, 0) except ValueError: raise LineFormatError('Invalid map value "%s"' % target) return code_point, target if __name__ == "__main__": if len(sys.argv) != 3: sys.stderr.write('Usage: %s \n' % sys.argv[0]) sys.exit(1) with open(sys.argv[1], 'r') as infile: for line in infile: src, dst = parse_line(line) if src is not None: MapTable.add_to_table(src, dst) with open(sys.argv[2], 'w') as outfile: outfile.write(AUTOGEN_HEADER % sys.argv[1]) for line in MapTable.output_table_as_list(): outfile.write(line + '\n')