_ast_gen.py 10 KB

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