aboutsummaryrefslogtreecommitdiffstats
path: root/tools/snippets_translate/handlers.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/snippets_translate/handlers.py')
-rw-r--r--tools/snippets_translate/handlers.py264
1 files changed, 176 insertions, 88 deletions
diff --git a/tools/snippets_translate/handlers.py b/tools/snippets_translate/handlers.py
index b3a827699..34e969a62 100644
--- a/tools/snippets_translate/handlers.py
+++ b/tools/snippets_translate/handlers.py
@@ -7,9 +7,44 @@ import sys
from parse_utils import (dstrip, get_indent, get_qt_module_class,
parse_arguments, remove_ref, replace_main_commas)
-IF_PATTERN = re.compile(r'^if\s*\(')
-ELSE_IF_PATTERN = re.compile(r'^}?\s*else if\s*\(')
-WHILE_PATTERN = re.compile(r'^while\s*\(')
+IF_PATTERN = re.compile(r'^\s*if\s*\(')
+PARENTHESES_NONEMPTY_CONTENT_PATTERN = re.compile(r"\((.+)\)")
+LOCAL_INCLUDE_PATTERN = re.compile(r'"(.*)"')
+GLOBAL_INCLUDE_PATTERN = re.compile(r"<(.*)>")
+IF_CONDITION_PATTERN = PARENTHESES_NONEMPTY_CONTENT_PATTERN
+ELSE_IF_PATTERN = re.compile(r'^\s*}?\s*else if\s*\(')
+WHILE_PATTERN = re.compile(r'^\s*while\s*\(')
+CAST_PATTERN = re.compile(r"[a-z]+_cast<(.*?)>\((.*?)\)") # Non greedy match of <>
+ITERATOR_LOOP_PATTERN = re.compile(r"= *(.*)egin\(")
+REMOVE_TEMPLATE_PARAMETER_PATTERN = re.compile("<.*>")
+PARENTHESES_CONTENT_PATTERN = re.compile(r"\((.*)\)")
+CONSTRUCTOR_BODY_PATTERN = re.compile(".*{ *}.*")
+CONSTRUCTOR_BODY_REPLACEMENT_PATTERN = re.compile("{ *}")
+CONSTRUCTOR_BASE_PATTERN = re.compile("^ *: *")
+NEGATE_PATTERN = re.compile(r"!(.)")
+CLASS_TEMPLATE_PATTERN = re.compile(r".*<.*>")
+EMPTY_CLASS_PATTERN = re.compile(r".*{.*}")
+EMPTY_CLASS_REPLACEMENT_PATTERN = re.compile(r"{.*}")
+FUNCTION_BODY_PATTERN = re.compile(r"\{(.*)\}")
+ARRAY_DECLARATION_PATTERN = re.compile(r"^[a-zA-Z0-9\<\>]+ ([\w\*]+) *\[?\]?")
+RETURN_TYPE_PATTERN = re.compile(r"^ *[a-zA-Z0-9]+ [\w]+::([\w\*\&]+\(.*\)$)")
+CAPTURE_PATTERN = re.compile(r"^ *([a-zA-Z0-9]+) ([\w\*\&]+\(.*\)$)")
+USELESS_QT_CLASSES_PATTERNS = [
+ re.compile(r'QLatin1StringView\(("[^"]*")\)'),
+ re.compile(r'QLatin1String\(("[^"]*")\)'),
+ re.compile(r'QString\.fromLatin1\(("[^"]*")\)'),
+ re.compile(r"QLatin1Char\(('[^']*')\)"),
+ re.compile(r'QStringLiteral\(("[^"]*")\)'),
+ re.compile(r'QString\.fromUtf8\(("[^"]*")\)'),
+ re.compile(r'u("[^"]*")_s')
+]
+COMMENT1_PATTERN = re.compile(r" *# *[\w\ ]+$")
+COMMENT2_PATTERN = re.compile(r" *# *(.*)$")
+COUT_ENDL_PATTERN = re.compile(r"cout *<<(.*)<< *.*endl")
+COUT1_PATTERN = re.compile(r" *<< *")
+COUT2_PATTERN = re.compile(r".*cout *<<")
+COUT_ENDL2_PATTERN = re.compile(r"<< +endl")
+NEW_PATTERN = re.compile(r"new +([a-zA-Z][a-zA-Z0-9_]*)")
def handle_condition(x, name):
@@ -26,10 +61,9 @@ def handle_condition(x, name):
comment = f" #{comment_content[-1]}"
x = x.replace(f"//{comment_content[-1]}", "")
- re_par = re.compile(r"\((.+)\)")
- match = re_par.search(x)
+ match = IF_CONDITION_PATTERN.search(x)
if match:
- condition = re_par.search(x).group(1)
+ condition = match.group(1)
return f"{get_indent(x)}{name} {condition.strip()}:{comment}"
else:
print(f'snippets_translate: Warning "{x}" does not match condition pattern',
@@ -57,34 +91,23 @@ def handle_inc_dec(x, operator):
def handle_casts(x):
- re_type = re.compile(r"<(.*)>")
- re_data = re.compile(r"_cast<.*>\((.*)\)")
- type_name = re_type.search(x)
- data_name = re_data.search(x)
-
- if type_name and data_name:
- type_name = type_name.group(1).replace("*", "")
- data_name = data_name.group(1)
- new_value = f"{type_name}({data_name})"
-
- if "static_cast" in x:
- x = re.sub(r"static_cast<.*>\(.*\)", new_value, x)
- elif "dynamic_cast" in x:
- x = re.sub(r"dynamic_cast<.*>\(.*\)", new_value, x)
- elif "const_cast" in x:
- x = re.sub(r"const_cast<.*>\(.*\)", new_value, x)
- elif "reinterpret_cast" in x:
- x = re.sub(r"reinterpret_cast<.*>\(.*\)", new_value, x)
- elif "qobject_cast" in x:
- x = re.sub(r"qobject_cast<.*>\(.*\)", new_value, x)
+ while True:
+ match = CAST_PATTERN.search(x)
+ if not match:
+ break
+ type_name = match.group(1).strip()
+ while type_name.endswith("*") or type_name.endswith("&") or type_name.endswith(" "):
+ type_name = type_name[:-1]
+ data_name = match.group(2).strip()
+ python_cast = f"{type_name}({data_name})"
+ x = x[0:match.start(0)] + python_cast + x[match.end(0):]
return x
def handle_include(x):
if '"' in x:
- re_par = re.compile(r'"(.*)"')
- header = re_par.search(x)
+ header = LOCAL_INCLUDE_PATTERN.search(x)
if header:
header_name = header.group(1).replace(".h", "")
module_name = header_name.replace('/', '.')
@@ -94,8 +117,7 @@ def handle_include(x):
# besides '"something.h"'
x = ""
elif "<" in x and ">" in x:
- re_par = re.compile(r"<(.*)>")
- name = re_par.search(x).group(1)
+ name = GLOBAL_INCLUDE_PATTERN.search(x).group(1)
t = get_qt_module_class(name)
# if it's not a Qt module or class, we discard it.
if t is None:
@@ -123,8 +145,7 @@ def handle_conditions(x):
def handle_for(x):
- re_content = re.compile(r"\((.*)\)")
- content = re_content.search(x)
+ content = PARENTHESES_CONTENT_PATTERN.search(x)
new_x = x
if content:
@@ -139,7 +160,7 @@ def handle_for(x):
# iterators
if "begin(" in x.lower() and "end(" in x.lower():
- name = re.search(r"= *(.*)egin\(", start)
+ name = ITERATOR_LOOP_PATTERN.search(start)
iterable = None
iterator = None
if name:
@@ -215,28 +236,30 @@ def handle_for(x):
elif x.count(":") > 0:
iterator, iterable = content.split(":", 1)
var = iterator.split()[-1].replace("&", "").strip()
- new_x = f"for {remove_ref(var)} in {iterable.strip()}:"
+ iterable = iterable.strip()
+ if iterable.startswith("qAsConst(") or iterable.startswith("std::as_const("):
+ iterable = iterable[iterable.find("(") + 1: -1]
+ new_x = f"for {remove_ref(var)} in {iterable}:"
return f"{get_indent(x)}{dstrip(new_x)}"
def handle_foreach(x):
- re_content = re.compile(r"\((.*)\)")
- content = re_content.search(x)
+ content = PARENTHESES_CONTENT_PATTERN.search(x)
if content:
parenthesis = content.group(1)
iterator, iterable = parenthesis.split(",", 1)
# remove iterator type
it = dstrip(iterator.split()[-1])
# remove <...> from iterable
- value = re.sub("<.*>", "", iterable)
+ value = REMOVE_TEMPLATE_PARAMETER_PATTERN.sub("", iterable)
return f"{get_indent(x)}for {it} in {value}:"
def handle_type_var_declaration(x):
# remove content between <...>
if "<" in x and ">" in x:
- x = " ".join(re.sub("<.*>", "", i) for i in x.split())
- content = re.search(r"\((.*)\)", x)
+ x = " ".join(REMOVE_TEMPLATE_PARAMETER_PATTERN.sub("", i) for i in x.split())
+ content = PARENTHESES_CONTENT_PATTERN.search(x)
if content:
# this means we have something like:
# QSome thing(...)
@@ -252,8 +275,7 @@ def handle_type_var_declaration(x):
def handle_constructors(x):
- re_content = re.compile(r"\((.*)\)")
- arguments = re_content.search(x).group(1)
+ arguments = PARENTHESES_CONTENT_PATTERN.search(x).group(1)
class_method = x.split("(")[0].split("::")
if len(class_method) == 2:
# Equal 'class name' and 'method name'
@@ -271,8 +293,8 @@ def handle_constructor_default_values(x):
# we discard that section completely, since even with a single
# value, we don't need to take care of it, for example:
# ' : a(1) { } -> self.a = 1
- if re.search(".*{ *}.*", x):
- x = re.sub("{ *}", "", x)
+ if CONSTRUCTOR_BODY_PATTERN.search(x):
+ x = CONSTRUCTOR_BODY_REPLACEMENT_PATTERN.sub("", x)
values = "".join(x.split(":", 1))
# Check the commas that are not inside round parenthesis
@@ -287,26 +309,24 @@ def handle_constructor_default_values(x):
if "@" in values:
return_values = ""
for arg in values.split("@"):
- arg = re.sub("^ *: *", "", arg).strip()
+ arg = CONSTRUCTOR_BASE_PATTERN.sub("", arg).strip()
if arg.startswith("Q"):
class_name = arg.split("(")[0]
content = arg.replace(class_name, "")[1:-1]
- return_values += f" {class_name}.__init__(self, {content})\n"
+ return_values += f" super().__init__({content})\n"
elif arg:
var_name = arg.split("(")[0]
- re_par = re.compile(r"\((.+)\)")
- content = re_par.search(arg).group(1)
+ content = PARENTHESES_NONEMPTY_CONTENT_PATTERN.search(arg).group(1)
return_values += f" self.{var_name} = {content}\n"
else:
- arg = re.sub("^ *: *", "", values).strip()
+ arg = CONSTRUCTOR_BASE_PATTERN.sub("", values).strip()
if arg.startswith("Q"):
class_name = arg.split("(")[0]
content = arg.replace(class_name, "")[1:-1]
- return f" {class_name}.__init__(self, {content})"
+ return f" super().__init__({content})"
elif arg:
var_name = arg.split("(")[0]
- re_par = re.compile(r"\((.+)\)")
- match = re_par.search(arg)
+ match = PARENTHESES_NONEMPTY_CONTENT_PATTERN.search(arg)
if match:
content = match.group(1)
return f" self.{var_name} = {content}"
@@ -320,27 +340,27 @@ def handle_constructor_default_values(x):
def handle_cout_endl(x):
# if comment at the end
comment = ""
- if re.search(r" *# *[\w\ ]+$", x):
- comment = f' # {re.search(" *# *(.*)$", x).group(1)}'
+ if COMMENT1_PATTERN.search(x):
+ match = COMMENT2_PATTERN.search(x).group(1)
+ comment = f' # {match}'
x = x.split("#")[0]
if "qDebug()" in x:
x = x.replace("qDebug()", "cout")
if "cout" in x and "endl" in x:
- re_cout_endl = re.compile(r"cout *<<(.*)<< *.*endl")
- data = re_cout_endl.search(x)
+ data = COUT_ENDL_PATTERN.search(x)
if data:
data = data.group(1)
- data = re.sub(" *<< *", ", ", data)
+ data = COUT1_PATTERN.sub(", ", data)
x = f"{get_indent(x)}print({data}){comment}"
elif "cout" in x:
- data = re.sub(".*cout *<<", "", x)
- data = re.sub(" *<< *", ", ", data)
+ data = COUT2_PATTERN.sub("", x)
+ data = COUT1_PATTERN.sub(", ", data)
x = f"{get_indent(x)}print({data}){comment}"
elif "endl" in x:
- data = re.sub("<< +endl", "", x)
- data = re.sub(" *<< *", ", ", data)
+ data = COUT_ENDL2_PATTERN.sub("", x)
+ data = COUT1_PATTERN.sub(", ", data)
x = f"{get_indent(x)}print({data}){comment}"
x = x.replace("( ", "(").replace(" )", ")").replace(" ,", ",").replace("(, ", "(")
@@ -356,8 +376,7 @@ def handle_negate(x):
elif "/*" in x:
if x.index("/*") < x.index("!"):
return x
- re_negate = re.compile(r"!(.)")
- next_char = re_negate.search(x).group(1)
+ next_char = NEGATE_PATTERN.search(x).group(1)
if next_char not in ("=", '"'):
x = x.replace("!", "not ")
return x
@@ -365,8 +384,7 @@ def handle_negate(x):
def handle_emit(x):
function_call = x.replace("emit ", "").strip()
- re_content = re.compile(r"\((.*)\)")
- match = re_content.search(function_call)
+ match = PARENTHESES_CONTENT_PATTERN.search(function_call)
if not match:
stmt = x.strip()
print(f'snippets_translate: Warning "{stmt}" does not match function call',
@@ -389,15 +407,14 @@ def handle_void_functions(x):
# if the arguments are in the same line:
arguments = None
if ")" in x:
- re_content = re.compile(r"\((.*)\)")
- parenthesis = re_content.search(x).group(1)
+ parenthesis = PARENTHESES_CONTENT_PATTERN.search(x).group(1)
arguments = dstrip(parse_arguments(parenthesis))
elif "," in x:
arguments = dstrip(parse_arguments(x.split("(")[-1]))
# check if includes a '{ ... }' after the method signature
after_signature = x.split(")")[-1]
- re_decl = re.compile(r"\{(.*)\}").search(after_signature)
+ re_decl = FUNCTION_BODY_PATTERN.search(after_signature)
extra = ""
if re_decl:
extra = re_decl.group(1)
@@ -433,13 +450,13 @@ def handle_class(x):
bases_name = ""
# Check if the class_name is templated, then remove it
- if re.search(r".*<.*>", class_name):
+ if CLASS_TEMPLATE_PATTERN.search(class_name):
class_name = class_name.split("<")[0]
# Special case: invalid notation for an example:
# class B() {...} -> clas B(): pass
- if re.search(r".*{.*}", class_name):
- class_name = re.sub(r"{.*}", "", class_name).rstrip()
+ if EMPTY_CLASS_PATTERN.search(class_name):
+ class_name = EMPTY_CLASS_REPLACEMENT_PATTERN.sub("", class_name).rstrip()
return f"{class_name}(): pass"
# Special case: check if the line ends in ','
@@ -455,8 +472,7 @@ def handle_class(x):
def handle_array_declarations(x):
- re_varname = re.compile(r"^[a-zA-Z0-9\<\>]+ ([\w\*]+) *\[?\]?")
- content = re_varname.search(x.strip())
+ content = ARRAY_DECLARATION_PATTERN.search(x.strip())
if content:
var_name = content.group(1)
rest_line = "".join(x.split("{")[1:])
@@ -465,13 +481,11 @@ def handle_array_declarations(x):
def handle_methods_return_type(x):
- re_capture = re.compile(r"^ *[a-zA-Z0-9]+ [\w]+::([\w\*\&]+\(.*\)$)")
- capture = re_capture.search(x)
+ capture = RETURN_TYPE_PATTERN.search(x)
if capture:
content = capture.group(1)
method_name = content.split("(")[0]
- re_par = re.compile(r"\((.+)\)")
- par_capture = re_par.search(x)
+ par_capture = PARENTHESES_NONEMPTY_CONTENT_PATTERN.search(x)
arguments = "(self)"
if par_capture:
arguments = f"(self, {par_capture.group(1)})"
@@ -480,13 +494,14 @@ def handle_methods_return_type(x):
def handle_functions(x):
- re_capture = re.compile(r"^ *[a-zA-Z0-9]+ ([\w\*\&]+\(.*\)$)")
- capture = re_capture.search(x)
+ capture = CAPTURE_PATTERN.search(x)
if capture:
- content = capture.group(1)
+ return_type = capture.group(1)
+ if return_type == "return": # "return QModelIndex();"
+ return x
+ content = capture.group(2)
function_name = content.split("(")[0]
- re_par = re.compile(r"\((.+)\)")
- par_capture = re_par.search(x)
+ par_capture = PARENTHESES_NONEMPTY_CONTENT_PATTERN.search(x)
arguments = ""
if par_capture:
for arg in par_capture.group(1).split(","):
@@ -499,10 +514,83 @@ def handle_functions(x):
def handle_useless_qt_classes(x):
- _classes = ("QLatin1String", "QLatin1Char")
- for i in _classes:
- re_content = re.compile(fr"{i}\((.*)\)")
- content = re_content.search(x)
- if content:
- x = x.replace(content.group(0), content.group(1))
- return x
+ for c in USELESS_QT_CLASSES_PATTERNS:
+ while True:
+ match = c.search(x)
+ if match:
+ x = x[0:match.start()] + match.group(1) + x[match.end():]
+ else:
+ break
+ return x.replace('"_L1', '"').replace("u'", "'")
+
+
+def handle_new(x):
+ """Parse operator new() and add parentheses were needed:
+ func(new Foo, new Bar(x))" -> "func(Foo(), Bar(x))"""
+ result = ""
+ last_pos = 0
+ for match in NEW_PATTERN.finditer(x):
+ end = match.end(0)
+ parentheses_needed = end >= len(x) or x[end] != "("
+ type_name = match.group(1)
+ result += x[last_pos:match.start(0)] + type_name
+ if parentheses_needed:
+ result += "()"
+ last_pos = end
+ result += x[last_pos:]
+ return result
+
+
+# The code below handles pairs of instance/pointer to member functions (PMF)
+# which appear in Qt in connect statements like:
+# "connect(fontButton, &QAbstractButton::clicked, this, &Dialog::setFont)".
+# In a first pass, these pairs are replaced by:
+# "connect(fontButton.clicked, self.setFont)" to be able to handle statements
+# spanning lines. A 2nd pass then checks for the presence of a connect
+# statement and replaces it by:
+# "fontButton.clicked.connect(self.setFont)".
+# To be called right after checking for comments.
+
+
+INSTANCE_PMF_RE = re.compile(r"&?(\w+),\s*&\w+::(\w+)")
+
+
+CONNECT_RE = re.compile(r"^(\s*)(QObject::)?connect\(([A-Za-z0-9_\.]+),\s*")
+
+
+def handle_qt_connects(line_in):
+ if not INSTANCE_PMF_RE.search(line_in):
+ return None
+ # 1st pass, "fontButton, &QAbstractButton::clicked" -> "fontButton.clicked"
+
+ is_connect = "connect(" in line_in
+ line = line_in
+ # Remove any smart pointer access, etc in connect statements
+ if is_connect:
+ line = line.replace(".get()", "").replace(".data()", "").replace("->", ".")
+ last_pos = 0
+ result = ""
+ for match in INSTANCE_PMF_RE.finditer(line):
+ instance = match.group(1)
+ if instance == "this":
+ instance = "self"
+ member_fun = match.group(2)
+ next_pos = match.start()
+ result += line[last_pos:next_pos]
+ last_pos = match.end()
+ result += f"{instance}.{member_fun}"
+ result += line[last_pos:]
+
+ if not is_connect:
+ return result
+
+ # 2nd pass, reorder connect.
+ connect_match = CONNECT_RE.match(result)
+ if not connect_match:
+ return result
+
+ space = connect_match.group(1)
+ signal_ = connect_match.group(3)
+ connect_stmt = f"{space}{signal_}.connect("
+ connect_stmt += result[connect_match.end():]
+ return connect_stmt