mirror of https://github.com/nirenjan/libx52.git
				
				
				
			
		
			
				
	
	
		
			202 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
			
		
		
	
	
			202 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
| #!/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 <input-map> <output-c-file>\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')
 |