_ast_gen.py 10 KB

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