#!/usr/bin/python import ast, os, re, sys # Storage for the relevant fields from an initializer for a single # element of a kc_item array. Fields not required for sorting or # generating the output header are not captured or saved. class ArrayInitializationLine: def __init__(self,xinput,y,name,enum,label,idx): # xinput, y are fields in the kc_item structure self.xinput = xinput self.y = y # name is the token that will be preprocessor-defined to the # appropriate udlr values self.name = name self.enum = enum self.label = label # index in the kc_item array at which this initializer was found self.idx = idx # For horizontal sorting, group elements by their vertical # coordinate, then break ties using the horizontal coordinate. This # causes elements from a single row to be contiguous in the sorted # output, and places visually adjacent rows adjacent in the sorted # output. def key_horizontal(self): return (self.y, self.xinput) # For vertical sorting, group elements by their horizontal # coordinate, then break ties using the vertical coordinate. def key_vertical(self): return (self.xinput, self.y) # InputException is raised when the post-processed C++ table cannot be # parsed using the regular expressions in this script. The table may or # may not be valid C++ when this script rejects it. class InputException(Exception): pass # NodeVisitor handles walking a Python Abstract Syntax Tree (AST) to # emulate a few supported operations, and raise an error on anything # unsupported. This is used to implement primitive support for # evaluating arithmetic expressions in the table. This support covers # only what is likely to be used and assumes Python syntax (which should # usually match C++ syntax closely enough, for the limited forms # supported). class NodeVisitor(ast.NodeVisitor): def __init__(self,source,lno,name,expr,constants): self.source = source self.lno = lno self.name = name self.expr = expr self.constants = constants def generic_visit(self,node): raise InputException('%s:%u: %r expression %r uses unsupported node type %s' % (self.source, self.lno, self.name, self.expr, node.__class__.__name__)) def visit_BinOp(self,node): left = self.visit(node.left) right = self.visit(node.right) op = node.op if isinstance(op, ast.Add): return left + right elif isinstance(op, ast.Sub): return left - right elif isinstance(op, ast.Mult): return left * right elif isinstance(op, ast.Div): return left / right else: raise InputException('%s:%u: %r expression %r uses unsupported BinOp node type %s' % (self.source, self.lno, self.name, self.expr, op.__class__.__name__)) # Resolve expressions by expanding them. def visit_Expression(self,node): return self.visit(node.body) # Resolve names by searching the dictionary `constants`, which is # initialized from C++ constexpr declarations found while scanning # the file. def visit_Name(self,node): try: return self.constants[node.id] except KeyError as e: raise InputException('%s:%u: %r expression %r uses undefined name %s' % (self.source, self.lno, self.name, self.expr, node.id)) # Resolve numbers by returning the value as-is. @staticmethod def visit_Num(node): return node.n class Main: def __init__(self): # Initially, no constants are known. Elements will be added by # prepare_text. self.constants = {} # Resolve an expression by parsing it into an AST, then visiting # the nodes and emulating the permitted operations. Any # disallowed operations will cause an exception, which will # propagate through resolve_expr into the caller. def resolve_expr(self,source,lno,name,expr): expr = expr.strip() return NodeVisitor(source, lno, name, expr, self.constants).visit(ast.parse(expr, mode='eval')) # Given a list of C++ initializer lines, extract from those lines # the position of each initialized cell, compute the u/d/l/r # relations among the cells, and return a multiline string # containing CPP #define statements appropriate to the cells. Each # C++ initializer must be entirely contained within a single element # in `lines`. # # array_name - C++ identifier for the array; used in a C # comment and, if labels are used, as part of the name of the macro # that expands to the label # # source - name of the file from which the lines were read # # lines - iterable of initializer lines # # _re_finditer_init_element - bound method for a regular expression to # extract the required fields def generate_defines(self,array_name,source,lines,_re_finditer_init_element=re.compile(r'{' r'(?:[^,]+),' # x r'(?P[^,]+),' r'(?P[^,]+),' r'(?:[^,]+),' # w2 r'\s*(?:DXX_KCONFIG_UI_ENUM\s*\((?P\w+)\)\s*)?(?:DXX_KCONFIG_UI_LABEL\s*\((?P