diff options
| author | Cristian Maureira-Fredes <Cristian.Maureira-Fredes@qt.io> | 2021-01-05 01:10:55 +0100 |
|---|---|---|
| committer | Qt Cherry-pick Bot <cherrypick_bot@qt-project.org> | 2021-03-18 12:54:29 +0000 |
| commit | c9788f47faa25f1f0151092a3cfda166b27b6d2e (patch) | |
| tree | 4555e90b6893db502d54c4530b8049f50d6972f8 /tools/snippets_translate/converter.py | |
| parent | dc3e4a064ee7e88e0950b860f8d0aa7557fb6f5c (diff) | |
Long live snippets_translate!
This is not a C++ -> Python translator,
but a line-by-line conversion tool.
This scripts requires two arguments to identify a Qt and PySide
directory including the sources.
There is a set of file extensions that are currently omitted from
the process, and for the ones that will be copied, there will be
messages related if the file already exists or if it's new.
If you use the '-v' option, you will see the C++ code and the
converted Python code, so it's easy to check for issues and missing
features.
Also, two command line options were added to have a different behavior
'--filter' to include a word to filter the full paths of all the
snippets found (for example the name of a directory),
and '-s/--single' to translate only a specific C++ file to be
translated.
Including test cases for transformations related to the C++
snippets.
Fixes: PYSIDE-691
Change-Id: I208e3a9139c7e84fe369a7c2ea93af240d83fa83
Reviewed-by: Christian Tismer <tismer@stackless.com>
(cherry picked from commit 1c65d71c468f2166ab20a867011a6d217a5f3ec1)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
Diffstat (limited to 'tools/snippets_translate/converter.py')
| -rw-r--r-- | tools/snippets_translate/converter.py | 334 |
1 files changed, 334 insertions, 0 deletions
diff --git a/tools/snippets_translate/converter.py b/tools/snippets_translate/converter.py new file mode 100644 index 000000000..e5193f598 --- /dev/null +++ b/tools/snippets_translate/converter.py @@ -0,0 +1,334 @@ +############################################################################# +## +## Copyright (C) 2021 The Qt Company Ltd. +## Contact: https://www.qt.io/licensing/ +## +## This file is part of Qt for Python. +## +## $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$ +## +############################################################################# + +import re + + +from handlers import (handle_casts, handle_class, handle_condition, + handle_conditions, handle_constructor_default_values, + handle_constructors, handle_cout_endl, handle_emit, + handle_for, handle_foreach, handle_inc_dec, + handle_include, handle_keywords, handle_negate, + handle_type_var_declaration, handle_void_functions, + handle_methods_return_type, handle_functions, + handle_array_declarations, handle_useless_qt_classes,) + +from parse_utils import get_indent, dstrip, remove_ref + + +def snippet_translate(x): + + ## Cases which are not C++ + ## TODO: Maybe expand this with lines that doesn't need to be translated + if x.strip().startswith("content-type: text/html"): + return x + + ## General Rules + + # Remove ';' at the end of the lines + if x.endswith(";"): + x = x[:-1] + + # Remove lines with only '{' or '}' + if x.strip() == "{" or x.strip() == "}": + return "" + + # Skip lines with the snippet related identifier '//!' + if x.strip().startswith("//!"): + return x + + # handle lines with only comments using '//' + if x.lstrip().startswith("//"): + x = x.replace("//", "#", 1) + return x + + # Handle "->" + if "->" in x: + x = x.replace("->", ".") + + # handle '&&' and '||' + if "&&" in x: + x = x.replace("&&", "and") + if "||" in x: + x = x.replace("||", "or") + + # Handle lines that have comments after the ';' + if ";" in x and "//" in x: + if x.index(";") < x.index("//"): + left, right = x.split("//", 1) + left = left.replace(";", "", 1) + x = f"{left}#{right}" + + # Handle 'new ' + # This contains an extra whitespace because of some variables + # that include the string 'new' + if "new " in x: + x = x.replace("new ", "") + + # Handle 'const' + # Some variables/functions have the word 'const' so we explicitly + # consider the cases with a whitespace before and after. + if " const" in x: + x = x.replace(" const", "") + if "const " in x: + x = x.replace("const ", "") + + # Handle 'static' + if "static " in x: + x = x.replace("static ", "") + + # Handle 'inline' + if "inline " in x: + x = x.replace("inline ", "") + + # Handle 'double' + if "double " in x: + x = x.replace("double ", "float ") + + # Handle increment/decrement operators + if "++" in x: + x = handle_inc_dec(x, "++") + if "--" in x: + x = handle_inc_dec(x, "--") + + # handle negate '!' + if "!" in x: + x = handle_negate(x) + + # Handle "this", "true", "false" but before "#" symbols + if "this" in x: + x = handle_keywords(x, "this", "self") + if "true" in x: + x = handle_keywords(x, "true", "True") + if "false" in x: + x = handle_keywords(x, "false", "False") + if "throw" in x: + x = handle_keywords(x, "throw", "raise") + + # handle 'void Class::method(...)' and 'void method(...)' + if re.search(r"^ *void *[\w\_]+(::)?[\w\d\_]+\(", x): + x = handle_void_functions(x) + + # 'Q*::' -> 'Q*.' + # FIXME: This will break iterators, but it's a small price. + if re.search(r"Q[\w]+::", x): + x = x.replace("::", ".") + + # handle 'nullptr' + if "nullptr" in x: + x = x.replace("nullptr", "None") + + ## Special Cases Rules + + # Special case for 'main' + if x.strip().startswith("int main("): + return f'{get_indent(x)}if __name__ == "__main__":' + + if x.strip().startswith("QApplication app(argc, argv)"): + return f"{get_indent(x)}app = QApplication([])" + + # Special case for 'return app.exec()' + if x.strip().startswith("return app.exec"): + return x.replace("return app.exec()", "sys.exit(app.exec_())") + + # Handle includes -> import + if x.strip().startswith("#include"): + x = handle_include(x) + return dstrip(x) + + if x.strip().startswith("emit "): + x = handle_emit(x) + return dstrip(x) + + # *_cast + if "_cast<" in x: + x = handle_casts(x) + + # Handle Qt classes that needs to be removed + x = handle_useless_qt_classes(x) + + # Handling ternary operator + if re.search(r"^.* \? .+ : .+$", x.strip()): + x = x.replace(" ? ", " if ") + x = x.replace(" : ", " else ") + + # Handle 'while', 'if', and 'else if' + # line might end in ')' or ") {" + if x.strip().startswith(("while", "if", "else if", "} else if")): + x = handle_conditions(x) + return dstrip(x) + elif re.search("^ *}? *else *{?", x): + x = re.sub(r"}? *else *{?", "else:", x) + return dstrip(x) + + # 'cout' and 'endl' + if re.search("^ *(std::)?cout", x) or ("endl" in x) or x.lstrip().startswith("qDebug()"): + x = handle_cout_endl(x) + return dstrip(x) + + # 'for' loops + if re.search(r"^ *for *\(", x.strip()): + return dstrip(handle_for(x)) + + # 'foreach' loops + if re.search(r"^ *foreach *\(", x.strip()): + return dstrip(handle_foreach(x)) + + # 'class' and 'structs' + if re.search(r"^ *class ", x) or re.search(r"^ *struct ", x): + if "struct " in x: + x = x.replace("struct ", "class ") + return handle_class(x) + + # 'delete' + if re.search(r"^ *delete ", x): + return x.replace("delete", "del") + + # 'public:' + if re.search(r"^public:$", x.strip()): + return x.replace("public:", "# public") + + # 'private:' + if re.search(r"^private:$", x.strip()): + return x.replace("private:", "# private") + + # For expressions like: `Type var` + # which does not contain a `= something` on the right side + # should match + # Some thing + # QSome<var> thing + # QSome thing(...) + # should not match + # QSome thing = a + # QSome thing = a(...) + # def something(a, b, c) + # At the end we skip methods with the form: + # QStringView Message::body() + # to threat them as methods. + if (re.search(r"^[a-zA-Z0-9]+(<.*?>)? [\w\*\&]+(\(.*?\))? ?(?!.*=|:).*$", x.strip()) + and x.strip().split()[0] not in ("def", "return", "and", "or") + and not re.search(r"^[a-zA-Z0-9]+(<.*?>)? [\w]+::[\w\*\&]+\(.*\)$", x.strip()) + and ("{" not in x and "}" not in x)): + + # FIXME: this 'if' is a hack for a function declaration with this form: + # QString myDecoderFunc(QByteArray &localFileName) + # No idea how to check for different for variables like + # QString notAFunction(Something something) + # Maybe checking the structure of the arguments? + if "Func" not in x: + return dstrip(handle_type_var_declaration(x)) + + # For expressions like: `Type var = value`, + # considering complex right-side expressions. + # QSome thing = b + # QSome thing = b(...) + # float v = 0.1 + # QSome *thing = ... + if (re.search(r"^[a-zA-Z0-9]+(<.*?>)? [\w\*]+ *= *[\w\.\"\']*(\(.*?\))?", x.strip()) and + ("{" not in x and "}" not in x)): + left, right = x.split("=", 1) + var_name = " ".join(left.strip().split()[1:]) + x = f"{get_indent(x)}{remove_ref(var_name)} = {right.strip()}" + # Special case: When having this: + # QVBoxLayout *layout = new QVBoxLayout; + # we end up like this: + # layout = QVBoxLayout + # so we need to add '()' at the end if it's just a word + # with only alpha numeric content + if re.search(r"\w+ = [A-Z]{1}\w+", x.strip()) and not x.strip().endswith(")"): + x = f"{x.rstrip()}()" + return dstrip(x) + + # For constructors, that we now the shape is: + # ClassName::ClassName(...) + if re.search(r"^ *\w+::\w+\(.*?\)", x.strip()): + x = handle_constructors(x) + return dstrip(x) + + # For base object constructor: + # : QWidget(parent) + if ( + x.strip().startswith(": ") + and ("<<" not in x) + and ("::" not in x) + and not x.strip().endswith(";") + ): + + return handle_constructor_default_values(x) + + # Arrays declarations with the form: + # type var_name[] = {... + # type var_name {... + #if re.search(r"^[a-zA-Z0-9]+(<.*?>)? [\w\*]+\[\] * = *\{", x.strip()): + if re.search(r"^[a-zA-Z0-9]+(<.*?>)? [\w\*]+\[?\]? * =? *\{", x.strip()): + x = handle_array_declarations(x) + + # Methods with return type + # int Class::method(...) + # QStringView Message::body() + if re.search(r"^[a-zA-Z0-9]+(<.*?>)? [\w]+::[\w\*\&]+\(.*\)$", x.strip()): + # We just need to capture the 'method name' and 'arguments' + x = handle_methods_return_type(x) + + # Handling functions + # By this section of the function, we cover all the other cases + # So we can safely assume it's not a variable declaration + if re.search(r"^[a-zA-Z0-9]+(<.*?>)? [\w\*\&]+\(.*\)$", x.strip()): + x = handle_functions(x) + + # General return for no special cases + return dstrip(x) + + # TODO: + # * Lambda expressions + + # * operator overload + # void operator()(int newState) { state = newState; } + # const QDBusArgument &operator>>(const QDBusArgument &argument, MyDictionary &myDict) + # inline bool operator==(const Employee &e1, const Employee &e2) + # void *operator new[](size_t size) + + # * extern "C" ... + # extern "C" MY_EXPORT int avg(int a, int b) + + # * typedef ... + # typedef int (*AvgFunction)(int, int); + + # * function pointers + # typedef void (*MyPrototype)(); |
