aboutsummaryrefslogtreecommitdiffstats
path: root/tools/qtpy2cpp_lib/formatter.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/qtpy2cpp_lib/formatter.py')
-rw-r--r--tools/qtpy2cpp_lib/formatter.py264
1 files changed, 264 insertions, 0 deletions
diff --git a/tools/qtpy2cpp_lib/formatter.py b/tools/qtpy2cpp_lib/formatter.py
new file mode 100644
index 000000000..81a920bbc
--- /dev/null
+++ b/tools/qtpy2cpp_lib/formatter.py
@@ -0,0 +1,264 @@
+#############################################################################
+##
+## Copyright (C) 2020 The Qt Company Ltd.
+## Contact: https://www.qt.io/licensing/
+##
+## This file is part of the Qt for Python project.
+##
+## $QT_BEGIN_LICENSE:LGPL$
+## Commercial License Usage
+## Licensees holding valid commercial Qt licenses may use this file in
+## accordance with the commercial license agreement provided with the
+## Software or, alternatively, in accordance with the terms contained in
+## a written agreement between you and The Qt Company. For licensing terms
+## and conditions see https://www.qt.io/terms-conditions. For further
+## information use the contact form at https://www.qt.io/contact-us.
+##
+## GNU Lesser General Public License Usage
+## Alternatively, this file may be used under the terms of the GNU Lesser
+## General Public License version 3 as published by the Free Software
+## Foundation and appearing in the file LICENSE.LGPL3 included in the
+## packaging of this file. Please review the following information to
+## ensure the GNU Lesser General Public License version 3 requirements
+## will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+##
+## GNU General Public License Usage
+## Alternatively, this file may be used under the terms of the GNU
+## General Public License version 2.0 or (at your option) the GNU General
+## Public license version 3 or any later version approved by the KDE Free
+## Qt Foundation. The licenses are as published by the Free Software
+## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+## included in the packaging of this file. Please review the following
+## information to ensure the GNU General Public License requirements will
+## be met: https://www.gnu.org/licenses/gpl-2.0.html and
+## https://www.gnu.org/licenses/gpl-3.0.html.
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+"""C++ formatting helper functions and formatter class"""
+
+
+import ast
+import sys
+
+
+CLOSING = {"{": "}", "(": ")", "[": "]"} # Closing parenthesis for C++
+
+
+def to_string(node):
+ """Helper to retrieve a string from the (Lists of)Name/Attribute
+ aggregated into some nodes"""
+ if isinstance(node, ast.Name):
+ return node.id
+ if isinstance(node, ast.Attribute):
+ return node.attr
+ return ''
+
+
+def format_inheritance(class_def_node):
+ """Returns inheritance specification of a class"""
+ result = ''
+ for base in class_def_node.bases:
+ name = to_string(base)
+ if name != 'object':
+ result += ', public ' if result else ' : public '
+ result += name
+ return result
+
+
+def format_for_target(target_node):
+ if isinstance(target_node, ast.Tuple): # for i,e in enumerate()
+ result = ''
+ for i, el in enumerate(target_node):
+ if i > 0:
+ result += ', '
+ result += format_reference(el)
+ return result
+ return format_reference(target_node)
+
+
+def format_for_loop(f_node):
+ """Format a for loop
+ This applies some heuristics to detect:
+ 1) "for a in [1,2])" -> "for (f: {1, 2}) {"
+ 2) "for i in range(5)" -> "for (i = 0; i < 5; ++i) {"
+ 3) "for i in range(2,5)" -> "for (i = 2; i < 5; ++i) {"
+
+ TODO: Detect other cases, maybe including enumerate().
+ """
+ loop_vars = format_for_target(f_node.target)
+ result = 'for (' + loop_vars
+ if isinstance(f_node.iter, ast.Call):
+ f = format_reference(f_node.iter.func)
+ if f == 'range':
+ start = 0
+ end = -1
+ if len(f_node.iter.args) == 2:
+ start = format_literal(f_node.iter.args[0])
+ end = format_literal(f_node.iter.args[1])
+ elif len(f_node.iter.args) == 1:
+ end = format_literal(f_node.iter.args[0])
+ result += f' = {start}; {loop_vars} < {end}; ++{loop_vars}'
+ elif isinstance(f_node.iter, ast.List):
+ # Range based for over list
+ result += ': ' + format_literal_list(f_node.iter)
+ result += ') {'
+ return result
+
+
+def format_literal(node):
+ """Returns the value of number/string literals"""
+ if isinstance(node, ast.Num):
+ return str(node.n)
+ if isinstance(node, ast.Str):
+ # Fixme: escaping
+ return f'"{node.s}"'
+ return ''
+
+
+def format_literal_list(l_node, enclosing='{'):
+ """Formats a list/tuple of number/string literals as C++ initializer list"""
+ result = enclosing
+ for i, el in enumerate(l_node.elts):
+ if i > 0:
+ result += ', '
+ result += format_literal(el)
+ result += CLOSING[enclosing]
+ return result
+
+
+def format_member(attrib_node, qualifier='auto'):
+ """Member access foo->member() is expressed as an attribute with
+ further nested Attributes/Names as value"""
+ n = attrib_node
+ result = ''
+ # Black magic: Guess '::' if name appears to be a class name
+ if qualifier == 'auto':
+ qualifier = '::' if n.attr[0:1].isupper() else '->'
+ while isinstance(n, ast.Attribute):
+ result = n.attr if not result else n.attr + qualifier + result
+ n = n.value
+ if isinstance(n, ast.Name) and n.id != 'self':
+ result = n.id + qualifier + result
+ return result
+
+
+def format_reference(node, qualifier='auto'):
+ """Format member reference or free item"""
+ return node.id if isinstance(node, ast.Name) else format_member(node, qualifier)
+
+
+def format_function_def_arguments(function_def_node):
+ """Formats arguments of a function definition"""
+ # Default values is a list of the last default values, expand
+ # so that indexes match
+ argument_count = len(function_def_node.args.args)
+ default_values = function_def_node.args.defaults
+ while len(default_values) < argument_count:
+ default_values.insert(0, None)
+ result = ''
+ for i, a in enumerate(function_def_node.args.args):
+ if result:
+ result += ', '
+ if a.arg != 'self':
+ result += a.arg
+ if default_values[i]:
+ result += ' = '
+ result += format_literal(default_values[i])
+ return result
+
+
+def format_start_function_call(call_node):
+ """Format a call of a free or member function"""
+ return format_reference(call_node.func) + '('
+
+
+def write_import(file, i_node):
+ """Print an import of a Qt class as #include"""
+ for alias in i_node.names:
+ if alias.name.startswith('Q'):
+ file.write(f'#include <{alias.name}>\n')
+
+
+def write_import_from(file, i_node):
+ """Print an import from Qt classes as #include sequence"""
+ # "from PySide2.QtGui import QGuiApplication" or
+ # "from PySide2 import QtGui"
+ mod = i_node.module
+ if not mod.startswith('PySide') and not mod.startswith('PyQt'):
+ return
+ dot = mod.find('.')
+ qt_module = mod[dot + 1:] + '/' if dot >= 0 else ''
+ for i in i_node.names:
+ if i.name.startswith('Q'):
+ file.write(f'#include <{qt_module}{i.name}>\n')
+
+
+class Indenter:
+ """Helper for Indentation"""
+
+ def __init__(self, output_file):
+ self._indent_level = 0
+ self._indentation = ''
+ self._output_file = output_file
+
+ def indent_string(self, string):
+ """Start a new line by a string"""
+ self._output_file.write(self._indentation)
+ self._output_file.write(string)
+
+ def indent_line(self, line):
+ """Write an indented line"""
+ self._output_file.write(self._indentation)
+ self._output_file.write(line)
+ self._output_file.write('\n')
+
+ def INDENT(self):
+ """Write indentation"""
+ self._output_file.write(self._indentation)
+
+ def indent(self):
+ """Increase indentation level"""
+ self._indent_level = self._indent_level + 1
+ self._indentation = ' ' * self._indent_level
+
+ def dedent(self):
+ """Decrease indentation level"""
+ self._indent_level = self._indent_level - 1
+ self._indentation = ' ' * self._indent_level
+
+
+class CppFormatter(Indenter):
+ """Provides helpers for formatting multi-line C++ constructs"""
+
+ def __init__(self, output_file):
+ Indenter.__init__(self, output_file)
+
+ def write_class_def(self, class_node):
+ """Print a class definition with inheritance"""
+ self._output_file.write('\n')
+ inherits = format_inheritance(class_node)
+ self.indent_line(f'class {class_node.name}{inherits}')
+ self.indent_line('{')
+ self.indent_line('public:')
+
+ def write_function_def(self, f_node, class_context):
+ """Print a function definition with arguments"""
+ self._output_file.write('\n')
+ arguments = format_function_def_arguments(f_node)
+ warn = True
+ if f_node.name == '__init__' and class_context: # Constructor
+ name = class_context
+ warn = len(arguments) > 0
+ elif f_node.name == '__del__' and class_context: # Destructor
+ name = '~' + class_context
+ warn = False
+ else:
+ name = 'void ' + f_node.name
+ self.indent_string(f'{name}({arguments})')
+ if warn:
+ self._output_file.write(' /* FIXME: types */')
+ self._output_file.write('\n')
+ self.indent_line('{')