aboutsummaryrefslogtreecommitdiffstats
path: root/tools/qtpy2cpp_lib/visitor.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/qtpy2cpp_lib/visitor.py')
-rw-r--r--tools/qtpy2cpp_lib/visitor.py260
1 files changed, 260 insertions, 0 deletions
diff --git a/tools/qtpy2cpp_lib/visitor.py b/tools/qtpy2cpp_lib/visitor.py
new file mode 100644
index 000000000..d17d5f53c
--- /dev/null
+++ b/tools/qtpy2cpp_lib/visitor.py
@@ -0,0 +1,260 @@
+#############################################################################
+##
+## 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$
+##
+#############################################################################
+
+"""AST visitor printing out C++"""
+
+import ast
+import sys
+import tokenize
+import warnings
+
+from .formatter import (CppFormatter, format_for_loop,
+ format_function_def_arguments, format_inheritance,
+ format_literal, format_reference,
+ format_start_function_call,
+ write_import, write_import_from)
+
+from .nodedump import debug_format_node
+
+
+class ConvertVisitor(ast.NodeVisitor, CppFormatter):
+ """AST visitor printing out C++
+ Note on implementation:
+ - Any visit_XXX() overridden function should call self.generic_visit(node)
+ to continue visiting
+ - When controlling the visiting manually (cf visit_Call()),
+ self.visit(child) needs to be called since that dispatches to
+ visit_XXX(). This is usually done to prevent undesired output
+ for example from references of calls, etc.
+ """
+
+ debug = False
+
+ def __init__(self, output_file):
+ ast.NodeVisitor.__init__(self)
+ CppFormatter.__init__(self, output_file)
+ self._class_scope = [] # List of class names
+ self._stack = [] # nodes
+ self._debug_indent = 0
+
+ @staticmethod
+ def create_ast(filename):
+ """Create an Abstract Syntax Tree on which a visitor can be run"""
+ node = None
+ with tokenize.open(filename) as file:
+ node = ast.parse(file.read(), mode="exec")
+ return node
+
+ def generic_visit(self, node):
+ parent = self._stack[-1] if self._stack else None
+ if self.debug:
+ self._debug_enter(node, parent)
+ self._stack.append(node)
+ try:
+ super().generic_visit(node)
+ except Exception as e:
+ line_no = node.lineno if hasattr(node, 'lineno') else -1
+ message = 'Error "{}" at line {}'.format(str(e), line_no)
+ warnings.warn(message)
+ self._output_file.write(f'\n// {message}\n')
+ del self._stack[-1]
+ if self.debug:
+ self._debug_leave(node)
+
+ def visit_Add(self, node):
+ self.generic_visit(node)
+ self._output_file.write(' + ')
+
+ def visit_Assign(self, node):
+ self._output_file.write('\n')
+ self.INDENT()
+ for target in node.targets:
+ if isinstance(target, ast.Tuple):
+ warnings.warn('List assignment not handled (line {}).'.
+ format(node.lineno))
+ elif isinstance(target, ast.Subscript):
+ warnings.warn('Subscript assignment not handled (line {}).'.
+ format(node.lineno))
+ else:
+ self._output_file.write(format_reference(target))
+ self._output_file.write(' = ')
+ self.visit(node.value)
+ self._output_file.write(';\n')
+
+ def visit_Attribute(self, node):
+ """Format a variable reference (cf visit_Name)"""
+ self._output_file.write(format_reference(node))
+
+ def visit_BinOp(self, node):
+ # Parentheses are not exposed, so, every binary operation needs to
+ # be enclosed by ().
+ self._output_file.write('(')
+ self.generic_visit(node)
+ self._output_file.write(')')
+
+ def visit_Call(self, node):
+ self._output_file.write(format_start_function_call(node))
+ # Manually do visit(), skip the children of func
+ for i, arg in enumerate(node.args):
+ if i > 0:
+ self._output_file.write(', ')
+ self.visit(arg)
+ self._output_file.write(')')
+
+ def visit_ClassDef(self, node):
+ # Manually do visit() to skip over base classes
+ # and annotations
+ self._class_scope.append(node.name)
+ self.write_class_def(node)
+ self.indent()
+ for b in node.body:
+ self.visit(b)
+ self.dedent()
+ self.indent_line('};')
+ del self._class_scope[-1]
+
+ def visit_Expr(self, node):
+ self._output_file.write('\n')
+ self.INDENT()
+ self.generic_visit(node)
+ self._output_file.write(';\n')
+
+ def visit_Gt(self, node):
+ self.generic_visit(node)
+ self._output_file.write('>')
+
+ def visit_For(self, node):
+ # Manually do visit() to get the indentation right.
+ # TODO: what about orelse?
+ self.indent_line(format_for_loop(node))
+ self.indent()
+ for b in node.body:
+ self.visit(b)
+ self.dedent()
+ self.indent_line('}')
+
+ def visit_FunctionDef(self, node):
+ class_context = self._class_scope[-1] if self._class_scope else None
+ self.write_function_def(node, class_context)
+ self.indent()
+ self.generic_visit(node)
+ self.dedent()
+ self.indent_line('}')
+
+ def visit_If(self, node):
+ # Manually do visit() to get the indentation right. Note:
+ # elsif() is modelled as nested if.
+ self.indent_string('if (')
+ self.visit(node.test)
+ self._output_file.write(') {\n')
+ self.indent()
+ for b in node.body:
+ self.visit(b)
+ self.dedent()
+ self.indent_string('}')
+ if node.orelse:
+ self._output_file.write(' else {\n')
+ self.indent()
+ for b in node.orelse:
+ self.visit(b)
+ self.dedent()
+ self.indent_string('}')
+ self._output_file.write('\n')
+
+ def visit_Import(self, node):
+ write_import(self._output_file, node)
+
+ def visit_ImportFrom(self, node):
+ write_import_from(self._output_file, node)
+
+ def visit_List(self, node):
+ # Manually do visit() to get separators right
+ self._output_file.write('{')
+ for i, el in enumerate(node.elts):
+ if i > 0:
+ self._output_file.write(', ')
+ self.visit(el)
+ self._output_file.write('}')
+
+ def visit_Lt(self, node):
+ self.generic_visit(node)
+ self._output_file.write('<')
+
+ def visit_Mult(self, node):
+ self.generic_visit(node)
+ self._output_file.write(' * ')
+
+ def visit_Name(self, node):
+ """Format a variable reference (cf visit_Attribute)"""
+ self._output_file.write(format_reference(node))
+
+ def visit_NameConstant(self, node):
+ self.generic_visit(node)
+ if node.value is None:
+ self._output_file.write('nullptr')
+ elif not node.value:
+ self._output_file.write('false')
+ else:
+ self._output_file.write('true')
+
+ def visit_Num(self, node):
+ self.generic_visit(node)
+ self._output_file.write(format_literal(node))
+
+ def visit_Str(self, node):
+ self.generic_visit(node)
+ self._output_file.write(format_literal(node))
+
+ def visit_UnOp(self, node):
+ self.generic_visit(node)
+
+ def _debug_enter(self, node, parent=None):
+ message = '{}>generic_visit({})'.format(' ' * self ._debug_indent,
+ debug_format_node(node))
+ if parent:
+ message += ', parent={}'.format(debug_format_node(parent))
+ message += '\n'
+ sys.stderr.write(message)
+ self._debug_indent += 1
+
+ def _debug_leave(self, node):
+ self._debug_indent -= 1
+ message = '{}<generic_visit({})\n'.format(' ' * self ._debug_indent,
+ type(node).__name__)
+ sys.stderr.write(message)