codekingpro/portable-devtools
114k
1# -----------------------------------------------------------------2# _ast_gen.py3#4# Generates the AST Node classes from a specification given in5# a configuration file. This module can also be run as a script to6# regenerate c_ast.py from _c_ast.cfg (from the repo root or the7# pycparser/ directory). Use 'make check' to reformat the generated8# file after running this script.9#10# The design of this module was inspired by astgen.py from the11# Python 2.5 code-base.12#13# Eli Bendersky [https://eli.thegreenplace.net/]14# License: BSD15# -----------------------------------------------------------------16from string import Template17import os18from typing import IO19 20 21class ASTCodeGenerator:22 def __init__(self, cfg_filename="_c_ast.cfg"):23 """Initialize the code generator from a configuration24 file.25 """26 self.cfg_filename = cfg_filename27 self.node_cfg = [28 NodeCfg(name, contents)29 for (name, contents) in self.parse_cfgfile(cfg_filename)30 ]31 32 def generate(self, file: IO[str]) -> None:33 """Generates the code into file, an open file buffer."""34 src = Template(_PROLOGUE_COMMENT).substitute(cfg_filename=self.cfg_filename)35 36 src += _PROLOGUE_CODE37 for node_cfg in self.node_cfg:38 src += node_cfg.generate_source() + "\n\n"39 40 file.write(src)41 42 def parse_cfgfile(self, filename):43 """Parse the configuration file and yield pairs of44 (name, contents) for each node.45 """46 with open(filename, "r") as f:47 for line in f:48 line = line.strip()49 if not line or line.startswith("#"):50 continue51 colon_i = line.find(":")52 lbracket_i = line.find("[")53 rbracket_i = line.find("]")54 if colon_i < 1 or lbracket_i <= colon_i or rbracket_i <= lbracket_i:55 raise RuntimeError(f"Invalid line in {filename}:\n{line}\n")56 57 name = line[:colon_i]58 val = line[lbracket_i + 1 : rbracket_i]59 vallist = [v.strip() for v in val.split(",")] if val else []60 yield name, vallist61 62 63class NodeCfg:64 """Node configuration.65 66 name: node name67 contents: a list of contents - attributes and child nodes68 See comment at the top of the configuration file for details.69 """70 71 def __init__(self, name, contents):72 self.name = name73 self.all_entries = []74 self.attr = []75 self.child = []76 self.seq_child = []77 78 for entry in contents:79 clean_entry = entry.rstrip("*")80 self.all_entries.append(clean_entry)81 82 if entry.endswith("**"):83 self.seq_child.append(clean_entry)84 elif entry.endswith("*"):85 self.child.append(clean_entry)86 else:87 self.attr.append(entry)88 89 def generate_source(self):90 src = self._gen_init()91 src += "\n" + self._gen_children()92 src += "\n" + self._gen_iter()93 src += "\n" + self._gen_attr_names()94 return src95 96 def _gen_init(self):97 src = f"class {self.name}(Node):\n"98 99 if self.all_entries:100 args = ", ".join(self.all_entries)101 slots = ", ".join(f"'{e}'" for e in self.all_entries)102 slots += ", 'coord', '__weakref__'"103 arglist = f"(self, {args}, coord=None)"104 else:105 slots = "'coord', '__weakref__'"106 arglist = "(self, coord=None)"107 108 src += f" __slots__ = ({slots})\n"109 src += f" def __init__{arglist}:\n"110 111 for name in self.all_entries + ["coord"]:112 src += f" self.{name} = {name}\n"113 114 return src115 116 def _gen_children(self):117 src = " def children(self):\n"118 119 if self.all_entries:120 src += " nodelist = []\n"121 122 for child in self.child:123 src += f" if self.{child} is not None:\n"124 src += f' nodelist.append(("{child}", self.{child}))\n'125 126 for seq_child in self.seq_child:127 src += f" for i, child in enumerate(self.{seq_child} or []):\n"128 src += f' nodelist.append((f"{seq_child}[{{i}}]", child))\n'129 130 src += " return tuple(nodelist)\n"131 else:132 src += " return ()\n"133 134 return src135 136 def _gen_iter(self):137 src = " def __iter__(self):\n"138 139 if self.all_entries:140 for child in self.child:141 src += f" if self.{child} is not None:\n"142 src += f" yield self.{child}\n"143 144 for seq_child in self.seq_child:145 src += f" for child in (self.{seq_child} or []):\n"146 src += " yield child\n"147 148 if not (self.child or self.seq_child):149 # Empty generator150 src += " return\n" + " yield\n"151 else:152 # Empty generator153 src += " return\n" + " yield\n"154 155 return src156 157 def _gen_attr_names(self):158 src = " attr_names = (" + "".join(f"{nm!r}, " for nm in self.attr) + ")"159 return src160 161 162_PROLOGUE_COMMENT = r"""#-----------------------------------------------------------------163# ** ATTENTION **164# This code was automatically generated from _c_ast.cfg165#166# Do not modify it directly. Modify the configuration file and167# run the generator again.168# ** ** *** ** **169#170# pycparser: c_ast.py171#172# AST Node classes.173#174# Eli Bendersky [https://eli.thegreenplace.net/]175# License: BSD176#-----------------------------------------------------------------177 178"""179_PROLOGUE_CODE = r'''180import sys181from typing import Any, ClassVar, IO, Optional182 183def _repr(obj):184 """185 Get the representation of an object, with dedicated pprint-like format for lists.186 """187 if isinstance(obj, list):188 return '[' + (',\n '.join((_repr(e).replace('\n', '\n ') for e in obj))) + '\n]'189 else:190 return repr(obj)191 192class Node:193 __slots__ = ()194 """ Abstract base class for AST nodes.195 """196 attr_names: ClassVar[tuple[str, ...]] = ()197 coord: Optional[Any]198 def __repr__(self):199 """ Generates a python representation of the current node200 """201 result = self.__class__.__name__ + '('202 203 indent = ''204 separator = ''205 for name in self.__slots__[:-2]:206 result += separator207 result += indent208 result += name + '=' + (_repr(getattr(self, name)).replace('\n', '\n ' + (' ' * (len(name) + len(self.__class__.__name__)))))209 210 separator = ','211 indent = '\n ' + (' ' * len(self.__class__.__name__))212 213 result += indent + ')'214 215 return result216 217 def children(self):218 """ A sequence of all children that are Nodes219 """220 pass221 222 def show(223 self,224 buf: IO[str] = sys.stdout,225 offset: int = 0,226 attrnames: bool = False,227 showemptyattrs: bool = True,228 nodenames: bool = False,229 showcoord: bool = False,230 _my_node_name: Optional[str] = None,231 ):232 """ Pretty print the Node and all its attributes and233 children (recursively) to a buffer.234 235 buf:236 Open IO buffer into which the Node is printed.237 238 offset:239 Initial offset (amount of leading spaces)240 241 attrnames:242 True if you want to see the attribute names in243 name=value pairs. False to only see the values.244 245 showemptyattrs:246 False if you want to suppress printing empty attributes.247 248 nodenames:249 True if you want to see the actual node names250 within their parents.251 252 showcoord:253 Do you want the coordinates of each Node to be254 displayed.255 """256 lead = ' ' * offset257 if nodenames and _my_node_name is not None:258 buf.write(lead + self.__class__.__name__+ ' <' + _my_node_name + '>: ')259 else:260 buf.write(lead + self.__class__.__name__+ ': ')261 262 if self.attr_names:263 def is_empty(v):264 v is None or (hasattr(v, '__len__') and len(v) == 0)265 nvlist = [(n, getattr(self,n)) for n in self.attr_names \266 if showemptyattrs or not is_empty(getattr(self,n))]267 if attrnames:268 attrstr = ', '.join(f'{name}={value}' for name, value in nvlist)269 else:270 attrstr = ', '.join(f'{value}' for _, value in nvlist)271 buf.write(attrstr)272 273 if showcoord:274 buf.write(f' (at {self.coord})')275 buf.write('\n')276 277 for (child_name, child) in self.children():278 child.show(279 buf,280 offset=offset + 2,281 attrnames=attrnames,282 showemptyattrs=showemptyattrs,283 nodenames=nodenames,284 showcoord=showcoord,285 _my_node_name=child_name)286 287 288class NodeVisitor:289 """ A base NodeVisitor class for visiting c_ast nodes.290 Subclass it and define your own visit_XXX methods, where291 XXX is the class name you want to visit with these292 methods.293 294 For example:295 296 class ConstantVisitor(NodeVisitor):297 def __init__(self):298 self.values = []299 300 def visit_Constant(self, node):301 self.values.append(node.value)302 303 Creates a list of values of all the constant nodes304 encountered below the given node. To use it:305 306 cv = ConstantVisitor()307 cv.visit(node)308 309 Notes:310 311 * generic_visit() will be called for AST nodes for which312 no visit_XXX method was defined.313 * The children of nodes for which a visit_XXX was314 defined will not be visited - if you need this, call315 generic_visit() on the node.316 You can use:317 NodeVisitor.generic_visit(self, node)318 * Modeled after Python's own AST visiting facilities319 (the ast module of Python 3.0)320 """321 322 _method_cache = None323 324 def visit(self, node: Node):325 """ Visit a node.326 """327 328 if self._method_cache is None:329 self._method_cache = {}330 331 visitor = self._method_cache.get(node.__class__.__name__, None)332 if visitor is None:333 method = 'visit_' + node.__class__.__name__334 visitor = getattr(self, method, self.generic_visit)335 self._method_cache[node.__class__.__name__] = visitor336 337 return visitor(node)338 339 def generic_visit(self, node: Node):340 """ Called if no explicit visitor function exists for a341 node. Implements preorder visiting of the node.342 """343 for _, c in node.children():344 self.visit(c)345 346'''347 348 349if __name__ == "__main__":350 base_dir = os.path.dirname(os.path.abspath(__file__))351 cfg_path = os.path.join(base_dir, "_c_ast.cfg")352 out_path = os.path.join(base_dir, "c_ast.py")353 ast_gen = ASTCodeGenerator(cfg_path)354 with open(out_path, "w") as out:355 ast_gen.generate(out)356 