formatter.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  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. """C++ formatting helper functions and formatter class"""
  5. import ast
  6. from .qt import ClassFlag, qt_class_flags
  7. CLOSING = {"{": "}", "(": ")", "[": "]"} # Closing parenthesis for C++
  8. def _fix_function_argument_type(type, for_return):
  9. """Fix function argument/return qualifiers using some heuristics for Qt."""
  10. if type == "float":
  11. return "double"
  12. if type == "str":
  13. type = "QString"
  14. if not type.startswith("Q"):
  15. return type
  16. flags = qt_class_flags(type)
  17. if flags & ClassFlag.PASS_BY_VALUE:
  18. return type
  19. if flags & ClassFlag.PASS_BY_CONSTREF:
  20. return type if for_return else f"const {type} &"
  21. if flags & ClassFlag.PASS_BY_REF:
  22. return type if for_return else f"{type} &"
  23. return type + " *" # Assume pointer by default
  24. def to_string(node):
  25. """Helper to retrieve a string from the (Lists of)Name/Attribute
  26. aggregated into some nodes"""
  27. if isinstance(node, ast.Name):
  28. return node.id
  29. if isinstance(node, ast.Attribute):
  30. return node.attr
  31. return ''
  32. def format_inheritance(class_def_node):
  33. """Returns inheritance specification of a class"""
  34. result = ''
  35. for base in class_def_node.bases:
  36. name = to_string(base)
  37. if name != 'object':
  38. result += ', public ' if result else ' : public '
  39. result += name
  40. return result
  41. def format_for_target(target_node):
  42. if isinstance(target_node, ast.Tuple): # for i,e in enumerate()
  43. result = ''
  44. for i, el in enumerate(target_node.elts):
  45. if i > 0:
  46. result += ', '
  47. result += format_reference(el)
  48. return result
  49. return format_reference(target_node)
  50. def format_for_loop(f_node):
  51. """Format a for loop
  52. This applies some heuristics to detect:
  53. 1) "for a in [1,2])" -> "for (f: {1, 2}) {"
  54. 2) "for i in range(5)" -> "for (i = 0; i < 5; ++i) {"
  55. 3) "for i in range(2,5)" -> "for (i = 2; i < 5; ++i) {"
  56. TODO: Detect other cases, maybe including enumerate().
  57. """
  58. loop_vars = format_for_target(f_node.target)
  59. result = 'for (' + loop_vars
  60. if isinstance(f_node.iter, ast.Call):
  61. f = format_reference(f_node.iter.func)
  62. if f == 'range':
  63. start = 0
  64. end = -1
  65. if len(f_node.iter.args) == 2:
  66. start = format_literal(f_node.iter.args[0])
  67. end = format_literal(f_node.iter.args[1])
  68. elif len(f_node.iter.args) == 1:
  69. end = format_literal(f_node.iter.args[0])
  70. result += f' = {start}; {loop_vars} < {end}; ++{loop_vars}'
  71. elif isinstance(f_node.iter, ast.List):
  72. # Range based for over list
  73. result += ': ' + format_literal_list(f_node.iter)
  74. elif isinstance(f_node.iter, ast.Name):
  75. # Range based for over variable
  76. result += ': ' + f_node.iter.id
  77. result += ') {'
  78. return result
  79. def format_name_constant(node):
  80. """Format a ast.NameConstant."""
  81. if node.value is None:
  82. return "nullptr"
  83. return "true" if node.value else "false"
  84. def format_literal(node):
  85. """Returns the value of number/string literals"""
  86. if isinstance(node, ast.NameConstant):
  87. return format_name_constant(node)
  88. if isinstance(node, ast.Num):
  89. return str(node.n)
  90. if isinstance(node, ast.Str):
  91. # Fixme: escaping
  92. return f'"{node.s}"'
  93. return ''
  94. def format_literal_list(l_node, enclosing='{'):
  95. """Formats a list/tuple of number/string literals as C++ initializer list"""
  96. result = enclosing
  97. for i, el in enumerate(l_node.elts):
  98. if i > 0:
  99. result += ', '
  100. result += format_literal(el)
  101. result += CLOSING[enclosing]
  102. return result
  103. def format_member(attrib_node, qualifier_in='auto'):
  104. """Member access foo->member() is expressed as an attribute with
  105. further nested Attributes/Names as value"""
  106. n = attrib_node
  107. result = ''
  108. # Black magic: Guess '::' if name appears to be a class name
  109. qualifier = qualifier_in
  110. if qualifier_in == 'auto':
  111. qualifier = '::' if n.attr[0:1].isupper() else '->'
  112. while isinstance(n, ast.Attribute):
  113. result = n.attr if not result else n.attr + qualifier + result
  114. n = n.value
  115. if isinstance(n, ast.Name) and n.id != 'self':
  116. if qualifier_in == 'auto' and n.id == "Qt": # Qt namespace
  117. qualifier = "::"
  118. result = n.id + qualifier + result
  119. return result
  120. def format_reference(node, qualifier='auto'):
  121. """Format member reference or free item"""
  122. return node.id if isinstance(node, ast.Name) else format_member(node, qualifier)
  123. def format_function_def_arguments(function_def_node):
  124. """Formats arguments of a function definition"""
  125. # Default values is a list of the last default values, expand
  126. # so that indexes match
  127. argument_count = len(function_def_node.args.args)
  128. default_values = function_def_node.args.defaults
  129. while len(default_values) < argument_count:
  130. default_values.insert(0, None)
  131. result = ''
  132. for i, a in enumerate(function_def_node.args.args):
  133. if result:
  134. result += ', '
  135. if a.arg != 'self':
  136. if a.annotation and isinstance(a.annotation, ast.Name):
  137. result += _fix_function_argument_type(a.annotation.id, False) + ' '
  138. result += a.arg
  139. if default_values[i]:
  140. result += ' = '
  141. default_value = default_values[i]
  142. if isinstance(default_value, ast.Attribute):
  143. result += format_reference(default_value)
  144. else:
  145. result += format_literal(default_value)
  146. return result
  147. def format_start_function_call(call_node):
  148. """Format a call of a free or member function"""
  149. return format_reference(call_node.func) + '('
  150. def write_import(file, i_node):
  151. """Print an import of a Qt class as #include"""
  152. for alias in i_node.names:
  153. if alias.name.startswith('Q'):
  154. file.write(f'#include <{alias.name}>\n')
  155. def write_import_from(file, i_node):
  156. """Print an import from Qt classes as #include sequence"""
  157. # "from PySide6.QtGui import QGuiApplication" or
  158. # "from PySide6 import QtGui"
  159. mod = i_node.module
  160. if not mod.startswith('PySide') and not mod.startswith('PyQt'):
  161. return
  162. dot = mod.find('.')
  163. qt_module = mod[dot + 1:] + '/' if dot >= 0 else ''
  164. for i in i_node.names:
  165. if i.name.startswith('Q'):
  166. file.write(f'#include <{qt_module}{i.name}>\n')
  167. class Indenter:
  168. """Helper for Indentation"""
  169. def __init__(self, output_file):
  170. self._indent_level = 0
  171. self._indentation = ''
  172. self._output_file = output_file
  173. def indent_string(self, string):
  174. """Start a new line by a string"""
  175. self._output_file.write(self._indentation)
  176. self._output_file.write(string)
  177. def indent_line(self, line):
  178. """Write an indented line"""
  179. self._output_file.write(self._indentation)
  180. self._output_file.write(line)
  181. self._output_file.write('\n')
  182. def INDENT(self):
  183. """Write indentation"""
  184. self._output_file.write(self._indentation)
  185. def indent(self):
  186. """Increase indentation level"""
  187. self._indent_level = self._indent_level + 1
  188. self._indentation = ' ' * self._indent_level
  189. def dedent(self):
  190. """Decrease indentation level"""
  191. self._indent_level = self._indent_level - 1
  192. self._indentation = ' ' * self._indent_level
  193. class CppFormatter(Indenter):
  194. """Provides helpers for formatting multi-line C++ constructs"""
  195. def __init__(self, output_file):
  196. Indenter.__init__(self, output_file)
  197. def write_class_def(self, class_node):
  198. """Print a class definition with inheritance"""
  199. self._output_file.write('\n')
  200. inherits = format_inheritance(class_node)
  201. self.indent_line(f'class {class_node.name}{inherits}')
  202. self.indent_line('{')
  203. self.indent_line('public:')
  204. def write_function_def(self, f_node, class_context):
  205. """Print a function definition with arguments"""
  206. self._output_file.write('\n')
  207. arguments = format_function_def_arguments(f_node)
  208. if f_node.name == '__init__' and class_context: # Constructor
  209. name = class_context
  210. elif f_node.name == '__del__' and class_context: # Destructor
  211. name = '~' + class_context
  212. else:
  213. return_type = "void"
  214. if f_node.returns and isinstance(f_node.returns, ast.Name):
  215. return_type = _fix_function_argument_type(f_node.returns.id, True)
  216. name = return_type + " " + f_node.name
  217. self.indent_string(f'{name}({arguments})')
  218. self._output_file.write('\n')
  219. self.indent_line('{')