First upload, 18 controller version
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,112 @@
|
||||
# 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
|
||||
from __future__ import annotations
|
||||
|
||||
"""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)
|
||||
@@ -0,0 +1,266 @@
|
||||
# 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
|
||||
from __future__ import annotations
|
||||
|
||||
"""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('{')
|
||||
@@ -0,0 +1,51 @@
|
||||
# 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
|
||||
from __future__ import annotations
|
||||
|
||||
"""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__
|
||||
@@ -0,0 +1,63 @@
|
||||
# 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
|
||||
from __future__ import annotations
|
||||
|
||||
"""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 = {
|
||||
# QtCore
|
||||
"QCoreApplication": ClassFlag.INSTANTIATE_ON_STACK,
|
||||
"QFile": ClassFlag.PASS_BY_REF | ClassFlag.INSTANTIATE_ON_STACK,
|
||||
"QFileInfo": ClassFlag.INSTANTIATE_ON_STACK,
|
||||
"QLine": ClassFlag.PASS_BY_CONSTREF | ClassFlag.INSTANTIATE_ON_STACK,
|
||||
"QLineF": ClassFlag.PASS_BY_CONSTREF | ClassFlag.INSTANTIATE_ON_STACK,
|
||||
"QModelIndex": ClassFlag.PASS_BY_CONSTREF | ClassFlag.INSTANTIATE_ON_STACK,
|
||||
"QPoint": ClassFlag.PASS_BY_VALUE | 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,
|
||||
"QSaveFile": ClassFlag.INSTANTIATE_ON_STACK,
|
||||
"QSettings": ClassFlag.PASS_BY_REF | ClassFlag.INSTANTIATE_ON_STACK,
|
||||
"QSize": ClassFlag.PASS_BY_VALUE | ClassFlag.INSTANTIATE_ON_STACK,
|
||||
"QSizeF": ClassFlag.PASS_BY_CONSTREF | ClassFlag.INSTANTIATE_ON_STACK,
|
||||
"QString": ClassFlag.PASS_BY_CONSTREF | ClassFlag.INSTANTIATE_ON_STACK,
|
||||
"QTextStream": ClassFlag.PASS_BY_REF | ClassFlag.INSTANTIATE_ON_STACK,
|
||||
# QtGui
|
||||
"QBrush": ClassFlag.PASS_BY_CONSTREF | ClassFlag.INSTANTIATE_ON_STACK,
|
||||
"QColor": ClassFlag.PASS_BY_VALUE | ClassFlag.INSTANTIATE_ON_STACK,
|
||||
"QGradient": ClassFlag.PASS_BY_CONSTREF | ClassFlag.INSTANTIATE_ON_STACK,
|
||||
"QGuiApplication": ClassFlag.INSTANTIATE_ON_STACK,
|
||||
"QIcon": ClassFlag.PASS_BY_CONSTREF | ClassFlag.INSTANTIATE_ON_STACK,
|
||||
"QPainter": ClassFlag.INSTANTIATE_ON_STACK,
|
||||
"QPen": ClassFlag.INSTANTIATE_ON_STACK,
|
||||
"QPixmap": ClassFlag.PASS_BY_CONSTREF | ClassFlag.INSTANTIATE_ON_STACK,
|
||||
# QtWidgets
|
||||
"QApplication": ClassFlag.INSTANTIATE_ON_STACK,
|
||||
"QColorDialog": ClassFlag.INSTANTIATE_ON_STACK,
|
||||
"QFileDialog": ClassFlag.INSTANTIATE_ON_STACK,
|
||||
"QFontDialog": ClassFlag.INSTANTIATE_ON_STACK,
|
||||
"QMessageBox": ClassFlag.INSTANTIATE_ON_STACK,
|
||||
# QtQml
|
||||
"QQmlApplicationEngine": ClassFlag.INSTANTIATE_ON_STACK,
|
||||
"QQmlComponent": ClassFlag.INSTANTIATE_ON_STACK,
|
||||
"QQmlEngine": ClassFlag.INSTANTIATE_ON_STACK,
|
||||
# QtQuick
|
||||
"QQuickView": ClassFlag.INSTANTIATE_ON_STACK
|
||||
}
|
||||
|
||||
|
||||
def qt_class_flags(type):
|
||||
f = _QT_CLASS_FLAGS.get(type)
|
||||
return f if f else ClassFlag(0)
|
||||
@@ -0,0 +1,56 @@
|
||||
# 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
|
||||
from __future__ import annotations
|
||||
|
||||
"""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))
|
||||
@@ -0,0 +1,443 @@
|
||||
# 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
|
||||
from __future__ import annotations
|
||||
|
||||
"""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, 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)
|
||||
Reference in New Issue
Block a user