#----------------------------------------------------------------- # _ast_gen.py # # Generates the AST Node classes from a specification given in # a configuration file # # The design of this module was inspired by astgen.py from the # Python 2.5 code-base. # # Eli Bendersky [https://eli.thegreenplace.net/] # License: BSD #----------------------------------------------------------------- from string import Template class ASTCodeGenerator(object): def __init__(self, cfg_filename='_c_ast.cfg'): """ Initialize the code generator from a configuration file. """ self.cfg_filename = cfg_filename self.node_cfg = [NodeCfg(name, contents) for (name, contents) in self.parse_cfgfile(cfg_filename)] def generate(self, file=None): """ Generates the code into file, an open file buffer. """ src = Template(_PROLOGUE_COMMENT).substitute( cfg_filename=self.cfg_filename) src += _PROLOGUE_CODE for node_cfg in self.node_cfg: src += node_cfg.generate_source() + '\n\n' file.write(src) def parse_cfgfile(self, filename): """ Parse the configuration file and yield pairs of (name, contents) for each node. """ with open(filename, "r") as f: for line in f: line = line.strip() if not line or line.startswith('#'): continue colon_i = line.find(':') lbracket_i = line.find('[') rbracket_i = line.find(']') if colon_i < 1 or lbracket_i <= colon_i or rbracket_i <= lbracket_i: raise RuntimeError("Invalid line in %s:\n%s\n" % (filename, line)) name = line[:colon_i] val = line[lbracket_i + 1:rbracket_i] vallist = [v.strip() for v in val.split(',')] if val else [] yield name, vallist class NodeCfg(object): """ Node configuration. name: node name contents: a list of contents - attributes and child nodes See comment at the top of the configuration file for details. """ def __init__(self, name, contents): self.name = name self.all_entries = [] self.attr = [] self.child = [] self.seq_child = [] for entry in contents: clean_entry = entry.rstrip('*') self.all_entries.append(clean_entry) if entry.endswith('**'): self.seq_child.append(clean_entry) elif entry.endswith('*'): self.child.append(clean_entry) else: self.attr.append(entry) def generate_source(self): src = self._gen_init() src += '\n' + self._gen_children() src += '\n' + self._gen_iter() src += '\n' + self._gen_attr_names() return src def _gen_init(self): src = "class %s(Node):\n" % self.name if self.all_entries: args = ', '.join(self.all_entries) slots = ', '.join("'{0}'".format(e) for e in self.all_entries) slots += ", 'coord', '__weakref__'" arglist = '(self, %s, coord=None)' % args else: slots = "'coord', '__weakref__'" arglist = '(self, coord=None)' src += " __slots__ = (%s)\n" % slots src += " def __init__%s:\n" % arglist for name in self.all_entries + ['coord']: src += " self.%s = %s\n" % (name, name) return src def _gen_children(self): src = ' def children(self):\n' if self.all_entries: src += ' nodelist = []\n' for child in self.child: src += ( ' if self.%(child)s is not None:' + ' nodelist.append(("%(child)s", self.%(child)s))\n') % ( dict(child=child)) for seq_child in self.seq_child: src += ( ' for i, child in enumerate(self.%(child)s or []):\n' ' nodelist.append(("%(child)s[%%d]" %% i, child))\n') % ( dict(child=seq_child)) src += ' return tuple(nodelist)\n' else: src += ' return ()\n' return src def _gen_iter(self): src = ' def __iter__(self):\n' if self.all_entries: for child in self.child: src += ( ' if self.%(child)s is not None:\n' + ' yield self.%(child)s\n') % (dict(child=child)) for seq_child in self.seq_child: src += ( ' for child in (self.%(child)s or []):\n' ' yield child\n') % (dict(child=seq_child)) if not (self.child or self.seq_child): # Empty generator src += ( ' return\n' + ' yield\n') else: # Empty generator src += ( ' return\n' + ' yield\n') return src def _gen_attr_names(self): src = " attr_names = (" + ''.join("%r, " % nm for nm in self.attr) + ')' return src _PROLOGUE_COMMENT = \ r'''#----------------------------------------------------------------- # ** ATTENTION ** # This code was automatically generated from the file: # $cfg_filename # # Do not modify it directly. Modify the configuration file and # run the generator again. # ** ** *** ** ** # # pycparser: c_ast.py # # AST Node classes. # # Eli Bendersky [https://eli.thegreenplace.net/] # License: BSD #----------------------------------------------------------------- ''' _PROLOGUE_CODE = r''' import sys def _repr(obj): """ Get the representation of an object, with dedicated pprint-like format for lists. """ if isinstance(obj, list): return '[' + (',\n '.join((_repr(e).replace('\n', '\n ') for e in obj))) + '\n]' else: return repr(obj) class Node(object): __slots__ = () """ Abstract base class for AST nodes. """ def __repr__(self): """ Generates a python representation of the current node """ result = self.__class__.__name__ + '(' indent = '' separator = '' for name in self.__slots__[:-2]: result += separator result += indent result += name + '=' + (_repr(getattr(self, name)).replace('\n', '\n ' + (' ' * (len(name) + len(self.__class__.__name__))))) separator = ',' indent = '\n ' + (' ' * len(self.__class__.__name__)) result += indent + ')' return result def children(self): """ A sequence of all children that are Nodes """ pass def show(self, buf=sys.stdout, offset=0, attrnames=False, nodenames=False, showcoord=False, _my_node_name=None): """ Pretty print the Node and all its attributes and children (recursively) to a buffer. buf: Open IO buffer into which the Node is printed. offset: Initial offset (amount of leading spaces) attrnames: True if you want to see the attribute names in name=value pairs. False to only see the values. nodenames: True if you want to see the actual node names within their parents. showcoord: Do you want the coordinates of each Node to be displayed. """ lead = ' ' * offset if nodenames and _my_node_name is not None: buf.write(lead + self.__class__.__name__+ ' <' + _my_node_name + '>: ') else: buf.write(lead + self.__class__.__name__+ ': ') if self.attr_names: if attrnames: nvlist = [(n, getattr(self,n)) for n in self.attr_names] attrstr = ', '.join('%s=%s' % nv for nv in nvlist) else: vlist = [getattr(self, n) for n in self.attr_names] attrstr = ', '.join('%s' % v for v in vlist) buf.write(attrstr) if showcoord: buf.write(' (at %s)' % self.coord) buf.write('\n') for (child_name, child) in self.children(): child.show( buf, offset=offset + 2, attrnames=attrnames, nodenames=nodenames, showcoord=showcoord, _my_node_name=child_name) class NodeVisitor(object): """ A base NodeVisitor class for visiting c_ast nodes. Subclass it and define your own visit_XXX methods, where XXX is the class name you want to visit with these methods. For example: class ConstantVisitor(NodeVisitor): def __init__(self): self.values = [] def visit_Constant(self, node): self.values.append(node.value) Creates a list of values of all the constant nodes encountered below the given node. To use it: cv = ConstantVisitor() cv.visit(node) Notes: * generic_visit() will be called for AST nodes for which no visit_XXX method was defined. * The children of nodes for which a visit_XXX was defined will not be visited - if you need this, call generic_visit() on the node. You can use: NodeVisitor.generic_visit(self, node) * Modeled after Python's own AST visiting facilities (the ast module of Python 3.0) """ _method_cache = None def visit(self, node): """ Visit a node. """ if self._method_cache is None: self._method_cache = {} visitor = self._method_cache.get(node.__class__.__name__, None) if visitor is None: method = 'visit_' + node.__class__.__name__ visitor = getattr(self, method, self.generic_visit) self._method_cache[node.__class__.__name__] = visitor return visitor(node) def generic_visit(self, node): """ Called if no explicit visitor function exists for a node. Implements preorder visiting of the node. """ for c in node: self.visit(c) '''