diff options
author | Friedemann Kleint <Friedemann.Kleint@qt.io> | 2022-09-02 09:43:41 +0200 |
---|---|---|
committer | Friedemann Kleint <Friedemann.Kleint@qt.io> | 2022-09-02 12:16:45 +0200 |
commit | 065766883f5e34d20ca88084cd747813a84ed2d3 (patch) | |
tree | 2bca2c34d113f0198564155ac21789466d4b8b96 /tools | |
parent | 4cfa700d5965922b5156517a64790369deac354f (diff) |
Move qtpy2cpp to sources/pyside-tools
Preparing the entry point.
Task-number: PYSIDE-1945
Change-Id: I4a2fbe6d35b4f97bf0ab7cfc2085b86a40bc2558
Reviewed-by: Christian Tismer <tismer@stackless.com>
Diffstat (limited to 'tools')
-rw-r--r-- | tools/qtpy2cpp.py | 62 | ||||
-rw-r--r-- | tools/qtpy2cpp.pyproject | 7 | ||||
-rw-r--r-- | tools/qtpy2cpp_lib/astdump.py | 111 | ||||
-rw-r--r-- | tools/qtpy2cpp_lib/formatter.py | 265 | ||||
-rw-r--r-- | tools/qtpy2cpp_lib/nodedump.py | 50 | ||||
-rw-r--r-- | tools/qtpy2cpp_lib/qt.py | 56 | ||||
-rw-r--r-- | tools/qtpy2cpp_lib/tests/baseline/basic_test.cpp | 62 | ||||
-rw-r--r-- | tools/qtpy2cpp_lib/tests/baseline/basic_test.py | 44 | ||||
-rw-r--r-- | tools/qtpy2cpp_lib/tests/test_qtpy2cpp.py | 54 | ||||
-rw-r--r-- | tools/qtpy2cpp_lib/tokenizer.py | 55 | ||||
-rw-r--r-- | tools/qtpy2cpp_lib/visitor.py | 443 |
11 files changed, 0 insertions, 1209 deletions
diff --git a/tools/qtpy2cpp.py b/tools/qtpy2cpp.py deleted file mode 100644 index 857b12b67..000000000 --- a/tools/qtpy2cpp.py +++ /dev/null @@ -1,62 +0,0 @@ -# Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -import logging -import os -import sys -from argparse import ArgumentParser, RawTextHelpFormatter -from pathlib import Path - -from qtpy2cpp_lib.visitor import ConvertVisitor - -DESCRIPTION = "Tool to convert Python to C++" - - -def create_arg_parser(desc): - parser = ArgumentParser(description=desc, - formatter_class=RawTextHelpFormatter) - parser.add_argument("--debug", "-d", action="store_true", - help="Debug") - parser.add_argument("--stdout", "-s", action="store_true", - help="Write to stdout") - parser.add_argument("--force", "-f", action="store_true", - help="Force overwrite of existing files") - parser.add_argument("files", type=str, nargs="+", help="Python source file(s)") - return parser - - -if __name__ == "__main__": - logging.basicConfig(level=logging.INFO) - logger = logging.getLogger(__name__) - arg_parser = create_arg_parser(DESCRIPTION) - args = arg_parser.parse_args() - ConvertVisitor.debug = args.debug - - for input_file_str in args.files: - input_file = Path(input_file_str) - if not input_file.is_file(): - logger.error(f"{input_file_str} does not exist or is not a file.") - sys.exit(-1) - file_root, ext = os.path.splitext(input_file) - if input_file.suffix != ".py": - logger.error(f"{input_file_str} does not appear to be a Python file.") - sys.exit(-1) - - ast_tree = ConvertVisitor.create_ast(input_file_str) - if args.stdout: - sys.stdout.write(f"// Converted from {input_file.name}\n") - ConvertVisitor(input_file, sys.stdout).visit(ast_tree) - else: - target_file = input_file.parent / (input_file.stem + ".cpp") - if target_file.exists(): - if not target_file.is_file(): - logger.error(f"{target_file} exists and is not a file.") - sys.exit(-1) - if not args.force: - logger.error(f"{target_file} exists. Use -f to overwrite.") - sys.exit(-1) - - with target_file.open("w") as file: - file.write(f"// Converted from {input_file.name}\n") - ConvertVisitor(input_file, file).visit(ast_tree) - logger.info(f"Wrote {target_file}.") diff --git a/tools/qtpy2cpp.pyproject b/tools/qtpy2cpp.pyproject deleted file mode 100644 index a059aebca..000000000 --- a/tools/qtpy2cpp.pyproject +++ /dev/null @@ -1,7 +0,0 @@ -{ - "files": ["qtpy2cpp.py", - "qtpy2cpp_lib/formatter.py", "qtpy2cpp_lib/visitor.py", "qtpy2cpp_lib/nodedump.py", - "qtpy2cpp_lib/astdump.py", "qtpy2cpp_lib/tokenizer.py", "qtpy2cpp_lib/qt.py", - "qtpy2cpp_lib/tests/test_qtpy2cpp.py", - "qtpy2cpp_lib/tests/baseline/basic_test.py", "qtpy2cpp_lib/tests/baseline/uic.py"] -} diff --git a/tools/qtpy2cpp_lib/astdump.py b/tools/qtpy2cpp_lib/astdump.py deleted file mode 100644 index d92fb7589..000000000 --- a/tools/qtpy2cpp_lib/astdump.py +++ /dev/null @@ -1,111 +0,0 @@ -# Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -"""Tool to dump a Python AST""" - - -import ast -import tokenize -from argparse import ArgumentParser, RawTextHelpFormatter -from enum import Enum - -from nodedump import debug_format_node - -DESCRIPTION = "Tool to dump a Python AST" - - -_source_lines = [] -_opt_verbose = False - - -def first_non_space(s): - for i, c in enumerate(s): - if c != ' ': - return i - return 0 - - -class NodeType(Enum): - IGNORE = 1 - PRINT_ONE_LINE = 2 # Print as a one liner, do not visit children - PRINT = 3 # Print with opening closing tag, visit children - PRINT_WITH_SOURCE = 4 # Like PRINT, but print source line above - - -def get_node_type(node): - if isinstance(node, (ast.Load, ast.Store, ast.Delete)): - return NodeType.IGNORE - if isinstance(node, (ast.Add, ast.alias, ast.arg, ast.Eq, ast.Gt, ast.Lt, - ast.Mult, ast.Name, ast.NotEq, ast.NameConstant, ast.Not, - ast.Num, ast.Str)): - return NodeType.PRINT_ONE_LINE - if not hasattr(node, 'lineno'): - return NodeType.PRINT - if isinstance(node, (ast.Attribute)): - return NodeType.PRINT_ONE_LINE if isinstance(node.value, ast.Name) else NodeType.PRINT - return NodeType.PRINT_WITH_SOURCE - - -class DumpVisitor(ast.NodeVisitor): - def __init__(self): - ast.NodeVisitor.__init__(self) - self._indent = 0 - self._printed_source_lines = {-1} - - def generic_visit(self, node): - node_type = get_node_type(node) - if _opt_verbose and node_type in (NodeType.IGNORE, NodeType.PRINT_ONE_LINE): - node_type = NodeType.PRINT - if node_type == NodeType.IGNORE: - return - self._indent = self._indent + 1 - indent = ' ' * self._indent - - if node_type == NodeType.PRINT_WITH_SOURCE: - line_number = node.lineno - 1 - if line_number not in self._printed_source_lines: - self._printed_source_lines.add(line_number) - line = _source_lines[line_number] - non_space = first_non_space(line) - print('{:04d} {}{}'.format(line_number, '_' * non_space, - line[non_space:])) - - if node_type == NodeType.PRINT_ONE_LINE: - print(indent, debug_format_node(node)) - else: - print(indent, '>', debug_format_node(node)) - ast.NodeVisitor.generic_visit(self, node) - print(indent, '<', type(node).__name__) - - self._indent = self._indent - 1 - - -def parse_ast(filename): - node = None - with tokenize.open(filename) as f: - global _source_lines - source = f.read() - _source_lines = source.split('\n') - node = ast.parse(source, mode="exec") - return node - - -def create_arg_parser(desc): - parser = ArgumentParser(description=desc, - formatter_class=RawTextHelpFormatter) - parser.add_argument('--verbose', '-v', action='store_true', - help='Verbose') - parser.add_argument('source', type=str, help='Python source') - return parser - - -if __name__ == '__main__': - arg_parser = create_arg_parser(DESCRIPTION) - options = arg_parser.parse_args() - _opt_verbose = options.verbose - title = f'AST tree for {options.source}' - print('=' * len(title)) - print(title) - print('=' * len(title)) - tree = parse_ast(options.source) - DumpVisitor().visit(tree) diff --git a/tools/qtpy2cpp_lib/formatter.py b/tools/qtpy2cpp_lib/formatter.py deleted file mode 100644 index 9a38e803d..000000000 --- a/tools/qtpy2cpp_lib/formatter.py +++ /dev/null @@ -1,265 +0,0 @@ -# Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -"""C++ formatting helper functions and formatter class""" - - -import ast - -from .qt import ClassFlag, qt_class_flags - -CLOSING = {"{": "}", "(": ")", "[": "]"} # Closing parenthesis for C++ - - -def _fix_function_argument_type(type, for_return): - """Fix function argument/return qualifiers using some heuristics for Qt.""" - if type == "float": - return "double" - if type == "str": - type = "QString" - if not type.startswith("Q"): - return type - flags = qt_class_flags(type) - if flags & ClassFlag.PASS_BY_VALUE: - return type - if flags & ClassFlag.PASS_BY_CONSTREF: - return type if for_return else f"const {type} &" - if flags & ClassFlag.PASS_BY_REF: - return type if for_return else f"{type} &" - return type + " *" # Assume pointer by default - - -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.elts): - 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) - elif isinstance(f_node.iter, ast.Name): - # Range based for over variable - result += ': ' + f_node.iter.id - result += ') {' - return result - - -def format_name_constant(node): - """Format a ast.NameConstant.""" - if node.value is None: - return "nullptr" - return "true" if node.value else "false" - - -def format_literal(node): - """Returns the value of number/string literals""" - if isinstance(node, ast.NameConstant): - return format_name_constant(node) - 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_in='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 - qualifier = qualifier_in - if qualifier_in == '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': - if qualifier_in == 'auto' and n.id == "Qt": # Qt namespace - qualifier = "::" - 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': - if a.annotation and isinstance(a.annotation, ast.Name): - result += _fix_function_argument_type(a.annotation.id, False) + ' ' - result += a.arg - if default_values[i]: - result += ' = ' - default_value = default_values[i] - if isinstance(default_value, ast.Attribute): - result += format_reference(default_value) - else: - result += format_literal(default_value) - 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 PySide6.QtGui import QGuiApplication" or - # "from PySide6 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) - if f_node.name == '__init__' and class_context: # Constructor - name = class_context - elif f_node.name == '__del__' and class_context: # Destructor - name = '~' + class_context - else: - return_type = "void" - if f_node.returns and isinstance(f_node.returns, ast.Name): - return_type = _fix_function_argument_type(f_node.returns.id, True) - name = return_type + " " + f_node.name - self.indent_string(f'{name}({arguments})') - self._output_file.write('\n') - self.indent_line('{') diff --git a/tools/qtpy2cpp_lib/nodedump.py b/tools/qtpy2cpp_lib/nodedump.py deleted file mode 100644 index de62e9700..000000000 --- a/tools/qtpy2cpp_lib/nodedump.py +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -"""Helper to dump AST nodes for debugging""" - - -import ast - - -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 debug_format_node(node): - """Format AST node for debugging""" - if isinstance(node, ast.alias): - return f'alias("{node.name}")' - if isinstance(node, ast.arg): - return f'arg({node.arg})' - if isinstance(node, ast.Attribute): - if isinstance(node.value, ast.Name): - nested_name = debug_format_node(node.value) - return f'Attribute("{node.attr}", {nested_name})' - return f'Attribute("{node.attr}")' - if isinstance(node, ast.Call): - return 'Call({}({}))'.format(to_string(node.func), len(node.args)) - if isinstance(node, ast.ClassDef): - base_names = [to_string(base) for base in node.bases] - bases = ': ' + ','.join(base_names) if base_names else '' - return f'ClassDef({node.name}{bases})' - if isinstance(node, ast.ImportFrom): - return f'ImportFrom("{node.module}")' - if isinstance(node, ast.FunctionDef): - arg_names = [a.arg for a in node.args.args] - return 'FunctionDef({}({}))'.format(node.name, ', '.join(arg_names)) - if isinstance(node, ast.Name): - return 'Name("{}", Ctx={})'.format(node.id, type(node.ctx).__name__) - if isinstance(node, ast.NameConstant): - return f'NameConstant({node.value})' - if isinstance(node, ast.Num): - return f'Num({node.n})' - if isinstance(node, ast.Str): - return f'Str("{node.s}")' - return type(node).__name__ diff --git a/tools/qtpy2cpp_lib/qt.py b/tools/qtpy2cpp_lib/qt.py deleted file mode 100644 index 69bd54aeb..000000000 --- a/tools/qtpy2cpp_lib/qt.py +++ /dev/null @@ -1,56 +0,0 @@ -# Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -"""Provides some type information on Qt classes""" - - -from enum import Flag - - -class ClassFlag(Flag): - PASS_BY_CONSTREF = 1 - PASS_BY_REF = 2 - PASS_BY_VALUE = 4 - PASS_ON_STACK_MASK = PASS_BY_CONSTREF | PASS_BY_REF | PASS_BY_VALUE - INSTANTIATE_ON_STACK = 8 - - -_QT_CLASS_FLAGS = { - "QBrush": ClassFlag.PASS_BY_CONSTREF | ClassFlag.INSTANTIATE_ON_STACK, - "QGradient": ClassFlag.PASS_BY_CONSTREF | ClassFlag.INSTANTIATE_ON_STACK, - "QIcon": ClassFlag.PASS_BY_CONSTREF | ClassFlag.INSTANTIATE_ON_STACK, - "QLine": ClassFlag.PASS_BY_CONSTREF | ClassFlag.INSTANTIATE_ON_STACK, - "QLineF": ClassFlag.PASS_BY_CONSTREF | ClassFlag.INSTANTIATE_ON_STACK, - "QPixmap": ClassFlag.PASS_BY_CONSTREF | ClassFlag.INSTANTIATE_ON_STACK, - "QPointF": ClassFlag.PASS_BY_CONSTREF | ClassFlag.INSTANTIATE_ON_STACK, - "QRect": ClassFlag.PASS_BY_CONSTREF | ClassFlag.INSTANTIATE_ON_STACK, - "QRectF": ClassFlag.PASS_BY_CONSTREF | ClassFlag.INSTANTIATE_ON_STACK, - "QSizeF": ClassFlag.PASS_BY_CONSTREF | ClassFlag.INSTANTIATE_ON_STACK, - "QString": ClassFlag.PASS_BY_CONSTREF | ClassFlag.INSTANTIATE_ON_STACK, - "QFile": ClassFlag.PASS_BY_REF | ClassFlag.INSTANTIATE_ON_STACK, - "QSettings": ClassFlag.PASS_BY_REF | ClassFlag.INSTANTIATE_ON_STACK, - "QTextStream": ClassFlag.PASS_BY_REF | ClassFlag.INSTANTIATE_ON_STACK, - "QColor": ClassFlag.PASS_BY_VALUE | ClassFlag.INSTANTIATE_ON_STACK, - "QPoint": ClassFlag.PASS_BY_VALUE | ClassFlag.INSTANTIATE_ON_STACK, - "QSize": ClassFlag.PASS_BY_VALUE | ClassFlag.INSTANTIATE_ON_STACK, - "QApplication": ClassFlag.INSTANTIATE_ON_STACK, - "QColorDialog": ClassFlag.INSTANTIATE_ON_STACK, - "QCoreApplication": ClassFlag.INSTANTIATE_ON_STACK, - "QFileDialog": ClassFlag.INSTANTIATE_ON_STACK, - "QFileInfo": ClassFlag.INSTANTIATE_ON_STACK, - "QFontDialog": ClassFlag.INSTANTIATE_ON_STACK, - "QGuiApplication": ClassFlag.INSTANTIATE_ON_STACK, - "QMessageBox": ClassFlag.INSTANTIATE_ON_STACK, - "QPainter": ClassFlag.INSTANTIATE_ON_STACK, - "QPen": ClassFlag.INSTANTIATE_ON_STACK, - "QQmlApplicationEngine": ClassFlag.INSTANTIATE_ON_STACK, - "QQmlComponent": ClassFlag.INSTANTIATE_ON_STACK, - "QQmlEngine": ClassFlag.INSTANTIATE_ON_STACK, - "QQuickView": ClassFlag.INSTANTIATE_ON_STACK, - "QSaveFile": ClassFlag.INSTANTIATE_ON_STACK -} - - -def qt_class_flags(type): - f = _QT_CLASS_FLAGS.get(type) - return f if f else ClassFlag(0) diff --git a/tools/qtpy2cpp_lib/tests/baseline/basic_test.cpp b/tools/qtpy2cpp_lib/tests/baseline/basic_test.cpp deleted file mode 100644 index 8ee7be31e..000000000 --- a/tools/qtpy2cpp_lib/tests/baseline/basic_test.cpp +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -// Converted from basic_test.py -#include <QtCore/Qt> -#include <QtGui/QColor> -#include <QtGui/QPainter> -#include <QtGui/QPaintEvent> -#include <QtGui/QShortcut> -#include <QtWidgets/QApplication> -#include <QtWidgets/QWidget> - -class Window : public QWidget -{ -public: - - Window(QWidget * parent = nullptr) - { - super()->__init__(parent); - } - - void paintEvent(QPaintEvent * e) - { - paint("bla"); - } - - void paint(const QString & what, color = Qt::blue) - { - { // Converted from context manager - p = QPainter(); - p->setPen(QColor(color)); - rect = rect(); - w = rect->width(); - h = rect->height(); - p->drawLine(0, 0, w, h); - p->drawLine(0, h, w, 0); - p->drawText(rect->center(), what); - } - } - - void sum() - { - values = {1, 2, 3}; - result = 0; - for (v: values) { - result += v - } - return result; - } -}; - -int main(int argc, char *argv[]) -{ - QApplication app(sys->argv); - window = Window(); - auto *sc = new QShortcut((Qt::CTRL | Qt::Key_Q), window); - sc->activated->connect(window->close); - window->setWindowTitle("Test"); - window->show(); - sys->exit(app.exec()); - return 0; -} diff --git a/tools/qtpy2cpp_lib/tests/baseline/basic_test.py b/tools/qtpy2cpp_lib/tests/baseline/basic_test.py deleted file mode 100644 index 10dc73767..000000000 --- a/tools/qtpy2cpp_lib/tests/baseline/basic_test.py +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env python -# Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -import sys - -from PySide6.QtCore import qVersion, Qt -from PySide6.QtGui import QColor, QPainter, QPaintEvent, QShortcut -from PySide6.QtWidgets import QApplication, QWidget - - -class Window(QWidget): - def __init__(self, parent: QWidget = None): - super().__init__(parent) - - def paintEvent(self, e: QPaintEvent): - self.paint("bla") - - def paint(self, what: str, color: Qt.GlobalColor = Qt.blue): - with QPainter(self) as p: - p.setPen(QColor(color)) - rect = self.rect() - w = rect.width() - h = rect.height() - p.drawLine(0, 0, w, h) - p.drawLine(0, h, w, 0) - p.drawText(rect.center(), what) - - def sum(self): - values = [1, 2, 3] - result = 0 - for v in values: - result += v - return result - - -if __name__ == '__main__': - app = QApplication(sys.argv) - window = Window() - sc = QShortcut(Qt.CTRL | Qt.Key_Q, window) - sc.activated.connect(window.close) - window.setWindowTitle("Test") - window.show() - sys.exit(app.exec()) diff --git a/tools/qtpy2cpp_lib/tests/test_qtpy2cpp.py b/tools/qtpy2cpp_lib/tests/test_qtpy2cpp.py deleted file mode 100644 index f9f921705..000000000 --- a/tools/qtpy2cpp_lib/tests/test_qtpy2cpp.py +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -import subprocess -import tempfile -import sys -from pathlib import Path - -# run pytest-3 - - -def diff_code(actual_code, expected_file): - """Helper to run diff if something fails (Linux only).""" - with tempfile.NamedTemporaryFile(suffix=".cpp") as tf: - tf.write(actual_code.encode('utf-8')) - tf.flush() - diff_cmd = ["diff", "-u", expected_file, tf.name] - subprocess.run(diff_cmd) - - -def run_converter(tool, file): - """Run the converter and return C++ code generated from file.""" - cmd = [sys.executable, tool, "--stdout", file] - output = "" - with subprocess.Popen(cmd, stdout=subprocess.PIPE) as proc: - output_b, errors_b = proc.communicate() - output = output_b.decode('utf-8') - if errors_b: - print(errors_b.decode('utf-8'), file=sys.stderr) - return output - - -def test_examples(): - dir = Path(__file__).resolve().parent - tool = dir.parents[1] / "qtpy2cpp.py" - assert(tool.is_file) - for test_file in (dir / "baseline").glob("*.py"): - assert(test_file.is_file) - expected_file = test_file.parent / (test_file.stem + ".cpp") - if expected_file.is_file(): - actual_code = run_converter(tool, test_file) - assert(actual_code) - expected_code = expected_file.read_text() - # Strip the license - code_start = expected_code.find("// Converted from") - assert(code_start != -1) - expected_code = expected_code[code_start:] - - if actual_code != expected_code: - diff_code(actual_code, expected_file) - assert(actual_code == expected_code) - else: - print(f"Warning, {test_file} is missing a .cpp file.", - file=sys.stderr) diff --git a/tools/qtpy2cpp_lib/tokenizer.py b/tools/qtpy2cpp_lib/tokenizer.py deleted file mode 100644 index d5e26c2a8..000000000 --- a/tools/qtpy2cpp_lib/tokenizer.py +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -"""Tool to dump Python Tokens""" - - -import sys -import tokenize - - -def format_token(t): - r = repr(t) - if r.startswith('TokenInfo('): - r = r[10:] - pos = r.find("), line='") - if pos < 0: - pos = r.find('), line="') - if pos > 0: - r = r[:pos + 1] - return r - - -def first_non_space(s): - for i, c in enumerate(s): - if c != ' ': - return i - return 0 - - -if __name__ == '__main__': - if len(sys.argv) < 2: - print("Specify file Name") - sys.exit(1) - filename = sys.argv[1] - indent_level = 0 - indent = '' - last_line_number = -1 - with tokenize.open(filename) as f: - generator = tokenize.generate_tokens(f.readline) - for t in generator: - line_number = t.start[0] - if line_number != last_line_number: - code_line = t.line.rstrip() - non_space = first_non_space(code_line) - print('{:04d} {}{}'.format(line_number, '_' * non_space, - code_line[non_space:])) - last_line_number = line_number - if t.type == tokenize.INDENT: - indent_level = indent_level + 1 - indent = ' ' * indent_level - elif t.type == tokenize.DEDENT: - indent_level = indent_level - 1 - indent = ' ' * indent_level - else: - print(' ', indent, format_token(t)) diff --git a/tools/qtpy2cpp_lib/visitor.py b/tools/qtpy2cpp_lib/visitor.py deleted file mode 100644 index 1e8b5dc84..000000000 --- a/tools/qtpy2cpp_lib/visitor.py +++ /dev/null @@ -1,443 +0,0 @@ -# Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -"""AST visitor printing out C++""" - -import ast -import sys -import tokenize -import warnings - -from .formatter import (CppFormatter, format_for_loop, format_literal, - format_name_constant, - format_reference, format_start_function_call, - write_import, write_import_from) -from .nodedump import debug_format_node -from .qt import ClassFlag, qt_class_flags - - -def _is_qt_constructor(assign_node): - """Is this assignment node a plain construction of a Qt class? - 'f = QFile(name)'. Returns the class_name.""" - call = assign_node.value - if (isinstance(call, ast.Call) and isinstance(call.func, ast.Name)): - func = call.func.id - if func.startswith("Q"): - return func - return None - - -def _is_if_main(if_node): - """Return whether an if statement is: if __name__ == '__main__' """ - test = if_node.test - return (isinstance(test, ast.Compare) - and len(test.ops) == 1 - and isinstance(test.ops[0], ast.Eq) - and isinstance(test.left, ast.Name) - and test.left.id == "__name__" - and len(test.comparators) == 1 - and isinstance(test.comparators[0], ast.Constant) - and test.comparators[0].value == "__main__") - - -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, file_name, output_file): - ast.NodeVisitor.__init__(self) - CppFormatter.__init__(self, output_file) - self._file_name = file_name - self._class_scope = [] # List of class names - self._stack = [] # nodes - self._stack_variables = [] # variables instantiated on stack - 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 - error_message = str(e) - message = f'{self._file_name}:{line_no}: Error "{error_message}"' - warnings.warn(message) - self._output_file.write(f'\n// {error_message}\n') - del self._stack[-1] - if self.debug: - self._debug_leave(node) - - def visit_Add(self, node): - self._handle_bin_op(node, "+") - - def _is_augmented_assign(self): - """Is it 'Augmented_assign' (operators +=/-=, etc)?""" - return self._stack and isinstance(self._stack[-1], ast.AugAssign) - - def visit_AugAssign(self, node): - """'Augmented_assign', Operators +=/-=, etc.""" - self.INDENT() - self.generic_visit(node) - self._output_file.write("\n") - - def visit_Assign(self, node): - self.INDENT() - - qt_class = _is_qt_constructor(node) - on_stack = qt_class and qt_class_flags(qt_class) & ClassFlag.INSTANTIATE_ON_STACK - - # Is this a free variable and not a member assignment? Instantiate - # on stack or give a type - if len(node.targets) == 1 and isinstance(node.targets[0], ast.Name): - if qt_class: - if on_stack: - # "QFile f(args)" - var = node.targets[0].id - self._stack_variables.append(var) - self._output_file.write(f"{qt_class} {var}(") - self._write_function_args(node.value.args) - self._output_file.write(");\n") - return - self._output_file.write("auto *") - - line_no = node.lineno if hasattr(node, 'lineno') else -1 - for target in node.targets: - if isinstance(target, ast.Tuple): - w = f"{self._file_name}:{line_no}: List assignment not handled." - warnings.warn(w) - elif isinstance(target, ast.Subscript): - w = f"{self._file_name}:{line_no}: Subscript assignment not handled." - warnings.warn(w) - else: - self._output_file.write(format_reference(target)) - self._output_file.write(' = ') - if qt_class and not on_stack: - self._output_file.write("new ") - self.visit(node.value) - self._output_file.write(';\n') - - def visit_Attribute(self, node): - """Format a variable reference (cf visit_Name)""" - # Default parameter (like Qt::black)? - if self._ignore_function_def_node(node): - return - 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 _handle_bin_op(self, node, op): - """Handle a binary operator which can appear as 'Augmented Assign'.""" - self.generic_visit(node) - full_op = f" {op}= " if self._is_augmented_assign() else f" {op} " - self._output_file.write(full_op) - - def visit_BitAnd(self, node): - self._handle_bin_op(node, "&") - - def visit_BitOr(self, node): - self._handle_bin_op(node, "|") - - def _format_call(self, node): - # Decorator list? - if self._ignore_function_def_node(node): - return - f = node.func - if isinstance(f, ast.Name): - self._output_file.write(f.id) - else: - # Attributes denoting chained calls "a->b()->c()". Walk along in - # reverse order, recursing for other calls. - names = [] - n = f - while isinstance(n, ast.Attribute): - names.insert(0, n.attr) - n = n.value - - if isinstance(n, ast.Name): # Member or variable reference - if n.id != "self": - sep = "->" - if n.id in self._stack_variables: - sep = "." - elif n.id[0:1].isupper(): # Heuristics for static - sep = "::" - self._output_file.write(n.id) - self._output_file.write(sep) - elif isinstance(n, ast.Call): # A preceding call - self._format_call(n) - self._output_file.write("->") - - self._output_file.write("->".join(names)) - - self._output_file.write('(') - self._write_function_args(node.args) - self._output_file.write(')') - - def visit_Call(self, node): - self._format_call(node) - # Context manager expression? - if self._within_context_manager(): - self._output_file.write(";\n") - - def _write_function_args(self, args_node): - # Manually do visit(), skip the children of func - for i, arg in enumerate(args_node): - if i > 0: - self._output_file.write(', ') - self.visit(arg) - - 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_Div(self, node): - self._handle_bin_op(node, "/") - - def visit_Eq(self, node): - self.generic_visit(node) - self._output_file.write(" == ") - - def visit_Expr(self, node): - 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_GtE(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 - for decorator in node.decorator_list: - func = decorator.func # (Call) - if isinstance(func, ast.Name) and func.id == "Slot": - self._output_file.write("\npublic slots:") - self.write_function_def(node, class_context) - # Find stack variables - for arg in node.args.args: - if arg.annotation and isinstance(arg.annotation, ast.Name): - type_name = arg.annotation.id - flags = qt_class_flags(type_name) - if flags & ClassFlag.PASS_ON_STACK_MASK: - self._stack_variables.append(arg.arg) - self.indent() - self.generic_visit(node) - self.dedent() - self.indent_line('}') - self._stack_variables.clear() - - def visit_If(self, node): - # Manually do visit() to get the indentation right. Note: - # elsif() is modelled as nested if. - - # Check for the main function - if _is_if_main(node): - self._output_file.write("\nint main(int argc, char *argv[])\n{\n") - self.indent() - for b in node.body: - self.visit(b) - self.indent_string("return 0;\n") - self.dedent() - self._output_file.write("}\n") - return - - 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_LShift(self, node): - self.generic_visit(node) - self._output_file.write(" << ") - - def visit_Lt(self, node): - self.generic_visit(node) - self._output_file.write(" < ") - - def visit_LtE(self, node): - self.generic_visit(node) - self._output_file.write(" <= ") - - def visit_Mult(self, node): - self._handle_bin_op(node, "*") - - def _within_context_manager(self): - """Return whether we are within a context manager (with).""" - parent = self._stack[-1] if self._stack else None - return parent and isinstance(parent, ast.withitem) - - def _ignore_function_def_node(self, node): - """Should this node be ignored within a FunctionDef.""" - if not self._stack: - return False - parent = self._stack[-1] - # A type annotation or default value of an argument? - if isinstance(parent, (ast.arguments, ast.arg)): - return True - if not isinstance(parent, ast.FunctionDef): - return False - # Return type annotation or decorator call - return node == parent.returns or node in parent.decorator_list - - def visit_Index(self, node): - self._output_file.write("[") - self.generic_visit(node) - self._output_file.write("]") - - def visit_Name(self, node): - """Format a variable reference (cf visit_Attribute)""" - # Skip Context manager variables, return or argument type annotation - if self._within_context_manager() or self._ignore_function_def_node(node): - return - self._output_file.write(format_reference(node)) - - def visit_NameConstant(self, node): - # Default parameter? - if self._ignore_function_def_node(node): - return - self.generic_visit(node) - self._output_file.write(format_name_constant(node)) - - def visit_Not(self, node): - self.generic_visit(node) - self._output_file.write("!") - - def visit_NotEq(self, node): - self.generic_visit(node) - self._output_file.write(" != ") - - def visit_Num(self, node): - self.generic_visit(node) - self._output_file.write(format_literal(node)) - - def visit_RShift(self, node): - self.generic_visit(node) - self._output_file.write(" >> ") - - def visit_Return(self, node): - self.indent_string("return") - if node.value: - self._output_file.write(" ") - self.generic_visit(node) - self._output_file.write(";\n") - - def visit_Slice(self, node): - self._output_file.write("[") - if node.lower: - self.visit(node.lower) - self._output_file.write(":") - if node.upper: - self.visit(node.upper) - self._output_file.write("]") - - def visit_Str(self, node): - self.generic_visit(node) - self._output_file.write(format_literal(node)) - - def visit_Sub(self, node): - self._handle_bin_op(node, "-") - - def visit_UnOp(self, node): - self.generic_visit(node) - - def visit_With(self, node): - self.INDENT() - self._output_file.write("{ // Converted from context manager\n") - self.indent() - for item in node.items: - self.INDENT() - if item.optional_vars: - self._output_file.write(format_reference(item.optional_vars)) - self._output_file.write(" = ") - self.generic_visit(node) - self.dedent() - self.INDENT() - self._output_file.write("}\n") - - 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) |