1#----------------------------------------------------------------- 2# _ast_gen.py 3# 4# Generates the AST Node classes from a specification given in 5# a configuration file 6# 7# The design of this module was inspired by astgen.py from the 8# Python 2.5 code-base. 9# 10# Eli Bendersky [https://eli.thegreenplace.net/] 11# License: BSD 12#----------------------------------------------------------------- 13import pprint 14from string import Template 15 16 17class ASTCodeGenerator(object): 18 def __init__(self, cfg_filename='_c_ast.cfg'): 19 """ Initialize the code generator from a configuration 20 file. 21 """ 22 self.cfg_filename = cfg_filename 23 self.node_cfg = [NodeCfg(name, contents) 24 for (name, contents) in self.parse_cfgfile(cfg_filename)] 25 26 def generate(self, file=None): 27 """ Generates the code into file, an open file buffer. 28 """ 29 src = Template(_PROLOGUE_COMMENT).substitute( 30 cfg_filename=self.cfg_filename) 31 32 src += _PROLOGUE_CODE 33 for node_cfg in self.node_cfg: 34 src += node_cfg.generate_source() + '\n\n' 35 36 file.write(src) 37 38 def parse_cfgfile(self, filename): 39 """ Parse the configuration file and yield pairs of 40 (name, contents) for each node. 41 """ 42 with open(filename, "r") as f: 43 for line in f: 44 line = line.strip() 45 if not line or line.startswith('#'): 46 continue 47 colon_i = line.find(':') 48 lbracket_i = line.find('[') 49 rbracket_i = line.find(']') 50 if colon_i < 1 or lbracket_i <= colon_i or rbracket_i <= lbracket_i: 51 raise RuntimeError("Invalid line in %s:\n%s\n" % (filename, line)) 52 53 name = line[:colon_i] 54 val = line[lbracket_i + 1:rbracket_i] 55 vallist = [v.strip() for v in val.split(',')] if val else [] 56 yield name, vallist 57 58 59class NodeCfg(object): 60 """ Node configuration. 61 62 name: node name 63 contents: a list of contents - attributes and child nodes 64 See comment at the top of the configuration file for details. 65 """ 66 67 def __init__(self, name, contents): 68 self.name = name 69 self.all_entries = [] 70 self.attr = [] 71 self.child = [] 72 self.seq_child = [] 73 74 for entry in contents: 75 clean_entry = entry.rstrip('*') 76 self.all_entries.append(clean_entry) 77 78 if entry.endswith('**'): 79 self.seq_child.append(clean_entry) 80 elif entry.endswith('*'): 81 self.child.append(clean_entry) 82 else: 83 self.attr.append(entry) 84 85 def generate_source(self): 86 src = self._gen_init() 87 src += '\n' + self._gen_children() 88 src += '\n' + self._gen_iter() 89 90 src += '\n' + self._gen_attr_names() 91 return src 92 93 def _gen_init(self): 94 src = "class %s(Node):\n" % self.name 95 96 if self.all_entries: 97 args = ', '.join(self.all_entries) 98 slots = ', '.join("'{0}'".format(e) for e in self.all_entries) 99 slots += ", 'coord', '__weakref__'" 100 arglist = '(self, %s, coord=None)' % args 101 else: 102 slots = "'coord', '__weakref__'" 103 arglist = '(self, coord=None)' 104 105 src += " __slots__ = (%s)\n" % slots 106 src += " def __init__%s:\n" % arglist 107 108 for name in self.all_entries + ['coord']: 109 src += " self.%s = %s\n" % (name, name) 110 111 return src 112 113 def _gen_children(self): 114 src = ' def children(self):\n' 115 116 if self.all_entries: 117 src += ' nodelist = []\n' 118 119 for child in self.child: 120 src += ( 121 ' if self.%(child)s is not None:' + 122 ' nodelist.append(("%(child)s", self.%(child)s))\n') % ( 123 dict(child=child)) 124 125 for seq_child in self.seq_child: 126 src += ( 127 ' for i, child in enumerate(self.%(child)s or []):\n' 128 ' nodelist.append(("%(child)s[%%d]" %% i, child))\n') % ( 129 dict(child=seq_child)) 130 131 src += ' return tuple(nodelist)\n' 132 else: 133 src += ' return ()\n' 134 135 return src 136 137 def _gen_iter(self): 138 src = ' def __iter__(self):\n' 139 140 if self.all_entries: 141 for child in self.child: 142 src += ( 143 ' if self.%(child)s is not None:\n' + 144 ' yield self.%(child)s\n') % (dict(child=child)) 145 146 for seq_child in self.seq_child: 147 src += ( 148 ' for child in (self.%(child)s or []):\n' 149 ' yield child\n') % (dict(child=seq_child)) 150 151 if not (self.child or self.seq_child): 152 # Empty generator 153 src += ( 154 ' return\n' + 155 ' yield\n') 156 else: 157 # Empty generator 158 src += ( 159 ' return\n' + 160 ' yield\n') 161 162 return src 163 164 def _gen_attr_names(self): 165 src = " attr_names = (" + ''.join("%r, " % nm for nm in self.attr) + ')' 166 return src 167 168 169_PROLOGUE_COMMENT = \ 170r'''#----------------------------------------------------------------- 171# ** ATTENTION ** 172# This code was automatically generated from the file: 173# $cfg_filename 174# 175# Do not modify it directly. Modify the configuration file and 176# run the generator again. 177# ** ** *** ** ** 178# 179# pycparser: c_ast.py 180# 181# AST Node classes. 182# 183# Eli Bendersky [https://eli.thegreenplace.net/] 184# License: BSD 185#----------------------------------------------------------------- 186 187''' 188 189_PROLOGUE_CODE = r''' 190import sys 191 192def _repr(obj): 193 """ 194 Get the representation of an object, with dedicated pprint-like format for lists. 195 """ 196 if isinstance(obj, list): 197 return '[' + (',\n '.join((_repr(e).replace('\n', '\n ') for e in obj))) + '\n]' 198 else: 199 return repr(obj) 200 201class Node(object): 202 __slots__ = () 203 """ Abstract base class for AST nodes. 204 """ 205 def __repr__(self): 206 """ Generates a python representation of the current node 207 """ 208 result = self.__class__.__name__ + '(' 209 210 indent = '' 211 separator = '' 212 for name in self.__slots__[:-2]: 213 result += separator 214 result += indent 215 result += name + '=' + (_repr(getattr(self, name)).replace('\n', '\n ' + (' ' * (len(name) + len(self.__class__.__name__))))) 216 217 separator = ',' 218 indent = '\n ' + (' ' * len(self.__class__.__name__)) 219 220 result += indent + ')' 221 222 return result 223 224 def children(self): 225 """ A sequence of all children that are Nodes 226 """ 227 pass 228 229 def show(self, buf=sys.stdout, offset=0, attrnames=False, nodenames=False, showcoord=False, _my_node_name=None): 230 """ Pretty print the Node and all its attributes and 231 children (recursively) to a buffer. 232 233 buf: 234 Open IO buffer into which the Node is printed. 235 236 offset: 237 Initial offset (amount of leading spaces) 238 239 attrnames: 240 True if you want to see the attribute names in 241 name=value pairs. False to only see the values. 242 243 nodenames: 244 True if you want to see the actual node names 245 within their parents. 246 247 showcoord: 248 Do you want the coordinates of each Node to be 249 displayed. 250 """ 251 lead = ' ' * offset 252 if nodenames and _my_node_name is not None: 253 buf.write(lead + self.__class__.__name__+ ' <' + _my_node_name + '>: ') 254 else: 255 buf.write(lead + self.__class__.__name__+ ': ') 256 257 if self.attr_names: 258 if attrnames: 259 nvlist = [(n, getattr(self,n)) for n in self.attr_names] 260 attrstr = ', '.join('%s=%s' % nv for nv in nvlist) 261 else: 262 vlist = [getattr(self, n) for n in self.attr_names] 263 attrstr = ', '.join('%s' % v for v in vlist) 264 buf.write(attrstr) 265 266 if showcoord: 267 buf.write(' (at %s)' % self.coord) 268 buf.write('\n') 269 270 for (child_name, child) in self.children(): 271 child.show( 272 buf, 273 offset=offset + 2, 274 attrnames=attrnames, 275 nodenames=nodenames, 276 showcoord=showcoord, 277 _my_node_name=child_name) 278 279 280class NodeVisitor(object): 281 """ A base NodeVisitor class for visiting c_ast nodes. 282 Subclass it and define your own visit_XXX methods, where 283 XXX is the class name you want to visit with these 284 methods. 285 286 For example: 287 288 class ConstantVisitor(NodeVisitor): 289 def __init__(self): 290 self.values = [] 291 292 def visit_Constant(self, node): 293 self.values.append(node.value) 294 295 Creates a list of values of all the constant nodes 296 encountered below the given node. To use it: 297 298 cv = ConstantVisitor() 299 cv.visit(node) 300 301 Notes: 302 303 * generic_visit() will be called for AST nodes for which 304 no visit_XXX method was defined. 305 * The children of nodes for which a visit_XXX was 306 defined will not be visited - if you need this, call 307 generic_visit() on the node. 308 You can use: 309 NodeVisitor.generic_visit(self, node) 310 * Modeled after Python's own AST visiting facilities 311 (the ast module of Python 3.0) 312 """ 313 314 _method_cache = None 315 316 def visit(self, node): 317 """ Visit a node. 318 """ 319 320 if self._method_cache is None: 321 self._method_cache = {} 322 323 visitor = self._method_cache.get(node.__class__.__name__, None) 324 if visitor is None: 325 method = 'visit_' + node.__class__.__name__ 326 visitor = getattr(self, method, self.generic_visit) 327 self._method_cache[node.__class__.__name__] = visitor 328 329 return visitor(node) 330 331 def generic_visit(self, node): 332 """ Called if no explicit visitor function exists for a 333 node. Implements preorder visiting of the node. 334 """ 335 for c in node: 336 self.visit(c) 337 338''' 339