visitor.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. # Copyright (C) 2022 The Qt Company Ltd.
  2. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
  3. from __future__ import annotations
  4. """AST visitor printing out C++"""
  5. import ast
  6. import sys
  7. import tokenize
  8. import warnings
  9. from .formatter import (CppFormatter, format_for_loop, format_literal,
  10. format_name_constant,
  11. format_reference, write_import, write_import_from)
  12. from .nodedump import debug_format_node
  13. from .qt import ClassFlag, qt_class_flags
  14. def _is_qt_constructor(assign_node):
  15. """Is this assignment node a plain construction of a Qt class?
  16. 'f = QFile(name)'. Returns the class_name."""
  17. call = assign_node.value
  18. if (isinstance(call, ast.Call) and isinstance(call.func, ast.Name)):
  19. func = call.func.id
  20. if func.startswith("Q"):
  21. return func
  22. return None
  23. def _is_if_main(if_node):
  24. """Return whether an if statement is: if __name__ == '__main__' """
  25. test = if_node.test
  26. return (isinstance(test, ast.Compare)
  27. and len(test.ops) == 1
  28. and isinstance(test.ops[0], ast.Eq)
  29. and isinstance(test.left, ast.Name)
  30. and test.left.id == "__name__"
  31. and len(test.comparators) == 1
  32. and isinstance(test.comparators[0], ast.Constant)
  33. and test.comparators[0].value == "__main__")
  34. class ConvertVisitor(ast.NodeVisitor, CppFormatter):
  35. """AST visitor printing out C++
  36. Note on implementation:
  37. - Any visit_XXX() overridden function should call self.generic_visit(node)
  38. to continue visiting
  39. - When controlling the visiting manually (cf visit_Call()),
  40. self.visit(child) needs to be called since that dispatches to
  41. visit_XXX(). This is usually done to prevent undesired output
  42. for example from references of calls, etc.
  43. """
  44. debug = False
  45. def __init__(self, file_name, output_file):
  46. ast.NodeVisitor.__init__(self)
  47. CppFormatter.__init__(self, output_file)
  48. self._file_name = file_name
  49. self._class_scope = [] # List of class names
  50. self._stack = [] # nodes
  51. self._stack_variables = [] # variables instantiated on stack
  52. self._debug_indent = 0
  53. @staticmethod
  54. def create_ast(filename):
  55. """Create an Abstract Syntax Tree on which a visitor can be run"""
  56. node = None
  57. with tokenize.open(filename) as file:
  58. node = ast.parse(file.read(), mode="exec")
  59. return node
  60. def generic_visit(self, node):
  61. parent = self._stack[-1] if self._stack else None
  62. if self.debug:
  63. self._debug_enter(node, parent)
  64. self._stack.append(node)
  65. try:
  66. super().generic_visit(node)
  67. except Exception as e:
  68. line_no = node.lineno if hasattr(node, 'lineno') else -1
  69. error_message = str(e)
  70. message = f'{self._file_name}:{line_no}: Error "{error_message}"'
  71. warnings.warn(message)
  72. self._output_file.write(f'\n// {error_message}\n')
  73. del self._stack[-1]
  74. if self.debug:
  75. self._debug_leave(node)
  76. def visit_Add(self, node):
  77. self._handle_bin_op(node, "+")
  78. def _is_augmented_assign(self):
  79. """Is it 'Augmented_assign' (operators +=/-=, etc)?"""
  80. return self._stack and isinstance(self._stack[-1], ast.AugAssign)
  81. def visit_AugAssign(self, node):
  82. """'Augmented_assign', Operators +=/-=, etc."""
  83. self.INDENT()
  84. self.generic_visit(node)
  85. self._output_file.write("\n")
  86. def visit_Assign(self, node):
  87. self.INDENT()
  88. qt_class = _is_qt_constructor(node)
  89. on_stack = qt_class and qt_class_flags(qt_class) & ClassFlag.INSTANTIATE_ON_STACK
  90. # Is this a free variable and not a member assignment? Instantiate
  91. # on stack or give a type
  92. if len(node.targets) == 1 and isinstance(node.targets[0], ast.Name):
  93. if qt_class:
  94. if on_stack:
  95. # "QFile f(args)"
  96. var = node.targets[0].id
  97. self._stack_variables.append(var)
  98. self._output_file.write(f"{qt_class} {var}(")
  99. self._write_function_args(node.value.args)
  100. self._output_file.write(");\n")
  101. return
  102. self._output_file.write("auto *")
  103. line_no = node.lineno if hasattr(node, 'lineno') else -1
  104. for target in node.targets:
  105. if isinstance(target, ast.Tuple):
  106. w = f"{self._file_name}:{line_no}: List assignment not handled."
  107. warnings.warn(w)
  108. elif isinstance(target, ast.Subscript):
  109. w = f"{self._file_name}:{line_no}: Subscript assignment not handled."
  110. warnings.warn(w)
  111. else:
  112. self._output_file.write(format_reference(target))
  113. self._output_file.write(' = ')
  114. if qt_class and not on_stack:
  115. self._output_file.write("new ")
  116. self.visit(node.value)
  117. self._output_file.write(';\n')
  118. def visit_Attribute(self, node):
  119. """Format a variable reference (cf visit_Name)"""
  120. # Default parameter (like Qt::black)?
  121. if self._ignore_function_def_node(node):
  122. return
  123. self._output_file.write(format_reference(node))
  124. def visit_BinOp(self, node):
  125. # Parentheses are not exposed, so, every binary operation needs to
  126. # be enclosed by ().
  127. self._output_file.write('(')
  128. self.generic_visit(node)
  129. self._output_file.write(')')
  130. def _handle_bin_op(self, node, op):
  131. """Handle a binary operator which can appear as 'Augmented Assign'."""
  132. self.generic_visit(node)
  133. full_op = f" {op}= " if self._is_augmented_assign() else f" {op} "
  134. self._output_file.write(full_op)
  135. def visit_BitAnd(self, node):
  136. self._handle_bin_op(node, "&")
  137. def visit_BitOr(self, node):
  138. self._handle_bin_op(node, "|")
  139. def _format_call(self, node):
  140. # Decorator list?
  141. if self._ignore_function_def_node(node):
  142. return
  143. f = node.func
  144. if isinstance(f, ast.Name):
  145. self._output_file.write(f.id)
  146. else:
  147. # Attributes denoting chained calls "a->b()->c()". Walk along in
  148. # reverse order, recursing for other calls.
  149. names = []
  150. n = f
  151. while isinstance(n, ast.Attribute):
  152. names.insert(0, n.attr)
  153. n = n.value
  154. if isinstance(n, ast.Name): # Member or variable reference
  155. if n.id != "self":
  156. sep = "->"
  157. if n.id in self._stack_variables:
  158. sep = "."
  159. elif n.id[0:1].isupper(): # Heuristics for static
  160. sep = "::"
  161. self._output_file.write(n.id)
  162. self._output_file.write(sep)
  163. elif isinstance(n, ast.Call): # A preceding call
  164. self._format_call(n)
  165. self._output_file.write("->")
  166. self._output_file.write("->".join(names))
  167. self._output_file.write('(')
  168. self._write_function_args(node.args)
  169. self._output_file.write(')')
  170. def visit_Call(self, node):
  171. self._format_call(node)
  172. # Context manager expression?
  173. if self._within_context_manager():
  174. self._output_file.write(";\n")
  175. def _write_function_args(self, args_node):
  176. # Manually do visit(), skip the children of func
  177. for i, arg in enumerate(args_node):
  178. if i > 0:
  179. self._output_file.write(', ')
  180. self.visit(arg)
  181. def visit_ClassDef(self, node):
  182. # Manually do visit() to skip over base classes
  183. # and annotations
  184. self._class_scope.append(node.name)
  185. self.write_class_def(node)
  186. self.indent()
  187. for b in node.body:
  188. self.visit(b)
  189. self.dedent()
  190. self.indent_line('};')
  191. del self._class_scope[-1]
  192. def visit_Div(self, node):
  193. self._handle_bin_op(node, "/")
  194. def visit_Eq(self, node):
  195. self.generic_visit(node)
  196. self._output_file.write(" == ")
  197. def visit_Expr(self, node):
  198. self.INDENT()
  199. self.generic_visit(node)
  200. self._output_file.write(';\n')
  201. def visit_Gt(self, node):
  202. self.generic_visit(node)
  203. self._output_file.write(" > ")
  204. def visit_GtE(self, node):
  205. self.generic_visit(node)
  206. self._output_file.write(" >= ")
  207. def visit_For(self, node):
  208. # Manually do visit() to get the indentation right.
  209. # TODO: what about orelse?
  210. self.indent_line(format_for_loop(node))
  211. self.indent()
  212. for b in node.body:
  213. self.visit(b)
  214. self.dedent()
  215. self.indent_line('}')
  216. def visit_FunctionDef(self, node):
  217. class_context = self._class_scope[-1] if self._class_scope else None
  218. for decorator in node.decorator_list:
  219. func = decorator.func # (Call)
  220. if isinstance(func, ast.Name) and func.id == "Slot":
  221. self._output_file.write("\npublic slots:")
  222. self.write_function_def(node, class_context)
  223. # Find stack variables
  224. for arg in node.args.args:
  225. if arg.annotation and isinstance(arg.annotation, ast.Name):
  226. type_name = arg.annotation.id
  227. flags = qt_class_flags(type_name)
  228. if flags & ClassFlag.PASS_ON_STACK_MASK:
  229. self._stack_variables.append(arg.arg)
  230. self.indent()
  231. self.generic_visit(node)
  232. self.dedent()
  233. self.indent_line('}')
  234. self._stack_variables.clear()
  235. def visit_If(self, node):
  236. # Manually do visit() to get the indentation right. Note:
  237. # elsif() is modelled as nested if.
  238. # Check for the main function
  239. if _is_if_main(node):
  240. self._output_file.write("\nint main(int argc, char *argv[])\n{\n")
  241. self.indent()
  242. for b in node.body:
  243. self.visit(b)
  244. self.indent_string("return 0;\n")
  245. self.dedent()
  246. self._output_file.write("}\n")
  247. return
  248. self.indent_string('if (')
  249. self.visit(node.test)
  250. self._output_file.write(') {\n')
  251. self.indent()
  252. for b in node.body:
  253. self.visit(b)
  254. self.dedent()
  255. self.indent_string('}')
  256. if node.orelse:
  257. self._output_file.write(' else {\n')
  258. self.indent()
  259. for b in node.orelse:
  260. self.visit(b)
  261. self.dedent()
  262. self.indent_string('}')
  263. self._output_file.write('\n')
  264. def visit_Import(self, node):
  265. write_import(self._output_file, node)
  266. def visit_ImportFrom(self, node):
  267. write_import_from(self._output_file, node)
  268. def visit_List(self, node):
  269. # Manually do visit() to get separators right
  270. self._output_file.write('{')
  271. for i, el in enumerate(node.elts):
  272. if i > 0:
  273. self._output_file.write(', ')
  274. self.visit(el)
  275. self._output_file.write('}')
  276. def visit_LShift(self, node):
  277. self.generic_visit(node)
  278. self._output_file.write(" << ")
  279. def visit_Lt(self, node):
  280. self.generic_visit(node)
  281. self._output_file.write(" < ")
  282. def visit_LtE(self, node):
  283. self.generic_visit(node)
  284. self._output_file.write(" <= ")
  285. def visit_Mult(self, node):
  286. self._handle_bin_op(node, "*")
  287. def _within_context_manager(self):
  288. """Return whether we are within a context manager (with)."""
  289. parent = self._stack[-1] if self._stack else None
  290. return parent and isinstance(parent, ast.withitem)
  291. def _ignore_function_def_node(self, node):
  292. """Should this node be ignored within a FunctionDef."""
  293. if not self._stack:
  294. return False
  295. parent = self._stack[-1]
  296. # A type annotation or default value of an argument?
  297. if isinstance(parent, (ast.arguments, ast.arg)):
  298. return True
  299. if not isinstance(parent, ast.FunctionDef):
  300. return False
  301. # Return type annotation or decorator call
  302. return node == parent.returns or node in parent.decorator_list
  303. def visit_Index(self, node):
  304. self._output_file.write("[")
  305. self.generic_visit(node)
  306. self._output_file.write("]")
  307. def visit_Name(self, node):
  308. """Format a variable reference (cf visit_Attribute)"""
  309. # Skip Context manager variables, return or argument type annotation
  310. if self._within_context_manager() or self._ignore_function_def_node(node):
  311. return
  312. self._output_file.write(format_reference(node))
  313. def visit_NameConstant(self, node):
  314. # Default parameter?
  315. if self._ignore_function_def_node(node):
  316. return
  317. self.generic_visit(node)
  318. self._output_file.write(format_name_constant(node))
  319. def visit_Not(self, node):
  320. self.generic_visit(node)
  321. self._output_file.write("!")
  322. def visit_NotEq(self, node):
  323. self.generic_visit(node)
  324. self._output_file.write(" != ")
  325. def visit_Num(self, node):
  326. self.generic_visit(node)
  327. self._output_file.write(format_literal(node))
  328. def visit_RShift(self, node):
  329. self.generic_visit(node)
  330. self._output_file.write(" >> ")
  331. def visit_Return(self, node):
  332. self.indent_string("return")
  333. if node.value:
  334. self._output_file.write(" ")
  335. self.generic_visit(node)
  336. self._output_file.write(";\n")
  337. def visit_Slice(self, node):
  338. self._output_file.write("[")
  339. if node.lower:
  340. self.visit(node.lower)
  341. self._output_file.write(":")
  342. if node.upper:
  343. self.visit(node.upper)
  344. self._output_file.write("]")
  345. def visit_Str(self, node):
  346. self.generic_visit(node)
  347. self._output_file.write(format_literal(node))
  348. def visit_Sub(self, node):
  349. self._handle_bin_op(node, "-")
  350. def visit_UnOp(self, node):
  351. self.generic_visit(node)
  352. def visit_With(self, node):
  353. self.INDENT()
  354. self._output_file.write("{ // Converted from context manager\n")
  355. self.indent()
  356. for item in node.items:
  357. self.INDENT()
  358. if item.optional_vars:
  359. self._output_file.write(format_reference(item.optional_vars))
  360. self._output_file.write(" = ")
  361. self.generic_visit(node)
  362. self.dedent()
  363. self.INDENT()
  364. self._output_file.write("}\n")
  365. def _debug_enter(self, node, parent=None):
  366. message = '{}>generic_visit({})'.format(' ' * self ._debug_indent,
  367. debug_format_node(node))
  368. if parent:
  369. message += ', parent={}'.format(debug_format_node(parent))
  370. message += '\n'
  371. sys.stderr.write(message)
  372. self._debug_indent += 1
  373. def _debug_leave(self, node):
  374. self._debug_indent -= 1
  375. message = '{}<generic_visit({})\n'.format(' ' * self ._debug_indent,
  376. type(node).__name__)
  377. sys.stderr.write(message)