Compare commits
3 Commits
f271b2e82c
...
e4fcfd11d7
Author | SHA1 | Date | |
---|---|---|---|
e4fcfd11d7 | |||
d3f1e824fa | |||
ad7b9126b7 |
BIN
DebugVarEdit.exe
BIN
DebugVarEdit.exe
Binary file not shown.
@ -8,14 +8,14 @@ import xml.etree.ElementTree as ET
|
||||
from generateVars import type_map
|
||||
from enum import IntEnum
|
||||
import threading
|
||||
from scanVars import run_scan
|
||||
from generateVars import run_generate
|
||||
import setupVars
|
||||
from VariableSelector import VariableSelectorDialog
|
||||
from VariableTable import VariableTableWidget, rows
|
||||
from scanVarGUI import ProcessOutputWindowDummy
|
||||
from scanVarGUI import ProcessOutputWindow
|
||||
import scanVars
|
||||
import myXML
|
||||
import time
|
||||
|
||||
from PySide2.QtWidgets import (
|
||||
QApplication, QWidget, QTableWidget, QTableWidgetItem,
|
||||
@ -201,25 +201,10 @@ class VarEditor(QWidget):
|
||||
|
||||
|
||||
# Создаём окно с кнопкой "Готово"
|
||||
self.proc_win = ProcessOutputWindowDummy(self.__after_scanvars_finished)
|
||||
self.emitting_stream = self.proc_win.emitting_stream # ключевая строка!
|
||||
self.proc_win.show()
|
||||
self.proc_win = ProcessOutputWindow(self.proj_path, self.makefile_path, self.xml_path,
|
||||
on_done_callback=self.__after_scanvars_finished)
|
||||
self.proc_win.start_scan()
|
||||
|
||||
def run_scan_wrapper():
|
||||
try:
|
||||
old_stdout = sys.stdout
|
||||
sys.stdout = self.emitting_stream
|
||||
|
||||
run_scan(self.proj_path, self.makefile_path, self.xml_path)
|
||||
|
||||
except Exception as e:
|
||||
self.emitting_stream.text_written.emit(f"\n[ОШИБКА] {e}")
|
||||
finally:
|
||||
sys.stdout = old_stdout
|
||||
self.emitting_stream.text_written.emit("\n--- Анализ завершён ---")
|
||||
self.proc_win.btn_close.setEnabled(True)
|
||||
|
||||
threading.Thread(target=run_scan_wrapper, daemon=True).start()
|
||||
|
||||
|
||||
def save_build(self):
|
||||
@ -432,17 +417,12 @@ class VarEditor(QWidget):
|
||||
def delete_selected_rows(self):
|
||||
selected_rows = sorted(set(index.row() for index in self.table.selectedIndexes()), reverse=True)
|
||||
if not selected_rows:
|
||||
return
|
||||
return
|
||||
|
||||
# Удаляем из vars_list те, у кого show_var == true и имя совпадает
|
||||
filtered_vars = [v for v in self.vars_list if v.get('show_var', 'false') == 'true']
|
||||
for row in selected_rows:
|
||||
if 0 <= row < len(filtered_vars):
|
||||
var_to_remove = filtered_vars[row]
|
||||
for v in self.vars_list:
|
||||
if v['name'] == var_to_remove['name']:
|
||||
v['show_var'] = 'false'
|
||||
break
|
||||
if 0 <= row < len(self.vars_list):
|
||||
# Меняем флаг show_var для переменной с этим индексом
|
||||
self.vars_list[row]['show_var'] = 'false'
|
||||
|
||||
self.table.populate(self.vars_list, self.structs, self.write_to_xml)
|
||||
self.write_to_xml()
|
||||
@ -453,7 +433,7 @@ class VarEditor(QWidget):
|
||||
QMessageBox.warning(self, "Нет переменных", f"Сначала загрузите переменные ({scan_title}).")
|
||||
return
|
||||
|
||||
dlg = VariableSelectorDialog(self.vars_list, self.structs, self.typedef_map, self.xml_path, self)
|
||||
dlg = VariableSelectorDialog(self.table, self.vars_list, self.structs, self.typedef_map, self.xml_path, self)
|
||||
if dlg.exec_():
|
||||
self.write_to_xml()
|
||||
self.update()
|
||||
|
@ -1,31 +1,36 @@
|
||||
import re
|
||||
import xml.etree.ElementTree as ET
|
||||
from PySide2.QtWidgets import (
|
||||
QDialog, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QPushButton,
|
||||
QLineEdit, QLabel, QHeaderView, QCompleter, QCheckBox, QHBoxLayout
|
||||
)
|
||||
from PySide2.QtGui import QKeySequence, QKeyEvent
|
||||
from PySide2.QtCore import Qt, QStringListModel, QSettings
|
||||
import VariableTable
|
||||
import setupVars
|
||||
import myXML
|
||||
import time
|
||||
|
||||
|
||||
array_re = re.compile(r'^(\w+)\[(\d+)\]$')
|
||||
|
||||
class VariableSelectorDialog(QDialog):
|
||||
def __init__(self, all_vars, structs, typedefs, xml_path=None, parent=None):
|
||||
def __init__(self, table, all_vars, structs, typedefs, xml_path=None, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("Выбор переменных")
|
||||
self.setAttribute(Qt.WA_DeleteOnClose)
|
||||
self.resize(600, 500)
|
||||
self.selected_names = []
|
||||
|
||||
self._bckspc_pressed = False # флаг подавления добавления разделителя
|
||||
self.table = table
|
||||
self.all_vars = all_vars
|
||||
self.structs = structs
|
||||
self.typedefs = typedefs
|
||||
self.expanded_vars = []
|
||||
self.var_map = {v['name']: v for v in all_vars}
|
||||
|
||||
self.node_index = {}
|
||||
self.xml_path = xml_path # сохраняем путь к xml
|
||||
self.manual_completion_active = False
|
||||
|
||||
# --- Добавляем чекбокс для автодополнения ---
|
||||
self.autocomplete_checkbox = QCheckBox("Включить автодополнение")
|
||||
@ -91,9 +96,32 @@ class VariableSelectorDialog(QDialog):
|
||||
self.setLayout(layout)
|
||||
|
||||
self.expanded_vars = setupVars.expand_vars(self.all_vars, self.structs, self.typedefs)
|
||||
|
||||
self.build_completion_list()
|
||||
self.populate_tree()
|
||||
|
||||
|
||||
def get_full_item_name(self, item):
|
||||
names = []
|
||||
while item:
|
||||
names.append(item.text(0))
|
||||
item = item.parent()
|
||||
return '.'.join(reversed(names))
|
||||
|
||||
def build_completion_list(self):
|
||||
# Собираем список полных имён всех переменных и вложенных полей
|
||||
completions = []
|
||||
|
||||
def recurse(var, prefix=''):
|
||||
fullname = f"{prefix}.{var['name']}" if prefix else var['name']
|
||||
completions.append(fullname)
|
||||
for child in var.get('children', []):
|
||||
recurse(child, fullname)
|
||||
|
||||
for v in self.expanded_vars:
|
||||
recurse(v)
|
||||
|
||||
self.all_completions = completions
|
||||
|
||||
def add_tree_item_recursively(self, parent, var):
|
||||
"""
|
||||
Рекурсивно добавляет переменную и её дочерние поля в дерево.
|
||||
@ -105,6 +133,8 @@ class VariableSelectorDialog(QDialog):
|
||||
|
||||
item = QTreeWidgetItem([name, type_str])
|
||||
item.setData(0, Qt.UserRole, name)
|
||||
full_name = self.get_full_item_name(item)
|
||||
self.node_index[full_name.lower()] = item
|
||||
|
||||
# Делаем bitfield-поля неактивными
|
||||
if "(bitfield:" in type_str:
|
||||
@ -128,254 +158,259 @@ class VariableSelectorDialog(QDialog):
|
||||
self.add_tree_item_recursively(item, child)
|
||||
|
||||
|
||||
def populate_tree(self):
|
||||
def populate_tree(self, vars_list=None):
|
||||
if vars_list is None:
|
||||
vars_list = self.expanded_vars
|
||||
self.tree.clear()
|
||||
|
||||
for var in self.expanded_vars:
|
||||
self.node_index.clear()
|
||||
for var in vars_list:
|
||||
self.add_tree_item_recursively(None, var)
|
||||
|
||||
header = self.tree.header()
|
||||
header.setSectionResizeMode(QHeaderView.Interactive) # вручную можно менять
|
||||
self.tree.setColumnWidth(0, 400)
|
||||
self.tree.resizeColumnToContents(1)
|
||||
""" header.setSectionResizeMode(0, QHeaderView.Stretch)
|
||||
header.setSectionResizeMode(1, QHeaderView.ResizeToContents) """
|
||||
|
||||
def expand_to_level(self, item, level, current_level=0):
|
||||
"""
|
||||
Рекурсивно раскрывает узлы до заданного уровня.
|
||||
"""
|
||||
if current_level < level:
|
||||
item.setExpanded(True)
|
||||
else:
|
||||
item.setExpanded(False)
|
||||
|
||||
for i in range(item.childCount()):
|
||||
self.expand_to_level(item.child(i), level, current_level + 1)
|
||||
|
||||
def filter_tree(self):
|
||||
text = self.search_input.text().strip().lower()
|
||||
path_parts = self.split_path(text) if text else []
|
||||
path_parts = text.split('.') if text else []
|
||||
filtered_vars = filter_vars(self.expanded_vars, path_parts)
|
||||
|
||||
def hide_all(item):
|
||||
item.setHidden(True)
|
||||
for i in range(item.childCount()):
|
||||
hide_all(item.child(i))
|
||||
# Сначала перерисовываем дерево
|
||||
self.populate_tree(filtered_vars)
|
||||
|
||||
def path_matches_search(name, search_parts):
|
||||
name_parts = self.split_path(name.lower())
|
||||
if len(name_parts) < len(search_parts):
|
||||
return False
|
||||
for sp, np in zip(search_parts, name_parts):
|
||||
if not np.startswith(sp):
|
||||
return False
|
||||
return True
|
||||
# Теперь node_index уже пересоздан — можно работать
|
||||
expand_level = len(path_parts) - 1 if path_parts else 0
|
||||
for i in range(self.tree.topLevelItemCount()):
|
||||
item = self.tree.topLevelItem(i)
|
||||
self.expand_to_level(item, expand_level)
|
||||
|
||||
def show_matching_path(item, level=0):
|
||||
name = item.text(0).lower()
|
||||
# Раскрываем путь до точного совпадения
|
||||
if path_parts:
|
||||
fullname = '.'.join(path_parts)
|
||||
node = self.node_index.get(fullname.lower())
|
||||
if node:
|
||||
parent = node.parent()
|
||||
while parent:
|
||||
parent.setExpanded(True)
|
||||
parent = parent.parent()
|
||||
|
||||
# По умолчанию не совпадает
|
||||
matched = False
|
||||
|
||||
|
||||
# Проверяем путь
|
||||
if not path_parts:
|
||||
matched = True
|
||||
elif path_matches_search(name, path_parts[:level+1]):
|
||||
matched = True
|
||||
|
||||
# Исключаем "плоские" элементы на верхнем уровне при глубоком поиске
|
||||
if level == 0 and item.childCount() == 0 and len(path_parts) > 1:
|
||||
matched = False
|
||||
|
||||
item.setHidden(not matched)
|
||||
|
||||
# Раскрываем узел, если он соответствует и ещё есть путь вниз
|
||||
if matched and level < len(path_parts) - 1:
|
||||
item.setExpanded(True)
|
||||
else:
|
||||
item.setExpanded(False)
|
||||
|
||||
matched_any_child = False
|
||||
for i in range(item.childCount()):
|
||||
child = item.child(i)
|
||||
if show_matching_path(child, level + 1):
|
||||
matched_any_child = True
|
||||
|
||||
return matched or matched_any_child
|
||||
|
||||
|
||||
# Если в поиске нет точки — особая логика для первого уровня
|
||||
if '.' not in text and '->' not in text and text != '':
|
||||
for i in range(self.tree.topLevelItemCount()):
|
||||
item = self.tree.topLevelItem(i)
|
||||
name = item.text(0).lower()
|
||||
if text in name:
|
||||
item.setHidden(False)
|
||||
item.setExpanded(False) # НЕ раскрываем потомков
|
||||
else:
|
||||
hide_all(item)
|
||||
item.setHidden(True)
|
||||
else:
|
||||
# Обычная логика с поиском по пути
|
||||
for i in range(self.tree.topLevelItemCount()):
|
||||
item = self.tree.topLevelItem(i)
|
||||
hide_all(item)
|
||||
show_matching_path(item, 0)
|
||||
def find_node_by_path(self, root_vars, path_list):
|
||||
current_level = root_vars
|
||||
node = None
|
||||
for part in path_list:
|
||||
node = None
|
||||
for var in current_level:
|
||||
if var['name'] == part:
|
||||
node = var
|
||||
break
|
||||
if node is None:
|
||||
return None
|
||||
current_level = node.get('children', [])
|
||||
return node
|
||||
|
||||
def update_completions(self, text = None):
|
||||
def update_completions(self, text=None):
|
||||
if text is None:
|
||||
text = self.search_input.text().strip()
|
||||
else:
|
||||
text = text.strip()
|
||||
|
||||
parts = self.split_path(text)
|
||||
path_parts = parts[:-1]
|
||||
prefix = parts[-1].lower() if not text.endswith(('.', '>')) else ''
|
||||
path_parts = parts[:-1] if parts else []
|
||||
prefix = parts[-1].lower() if parts else ''
|
||||
ends_with_sep = text.endswith('.') or text.endswith('->') or text.endswith('[')
|
||||
is_index_suggestion = text.endswith('[')
|
||||
|
||||
# Если путь есть (например: project.adc или project.adc.), ищем внутри него
|
||||
search_deep = len(path_parts) > 0
|
||||
completions = []
|
||||
|
||||
def find_path_items(path_parts):
|
||||
items = [self.tree.topLevelItem(i) for i in range(self.tree.topLevelItemCount())]
|
||||
def find_exact_node(parts):
|
||||
# Ищем точный узел по полному пути, используя node_index
|
||||
# Постепенно собираем fullname из parts
|
||||
if not parts:
|
||||
return None
|
||||
fullname = parts[0]
|
||||
for p in parts[1:]:
|
||||
fullname += '.' + p
|
||||
return self.node_index.get(fullname.lower())
|
||||
|
||||
for part in path_parts:
|
||||
part_lower = part.lower()
|
||||
matched = []
|
||||
if is_index_suggestion:
|
||||
base_text = text[:-1] # убираем '['
|
||||
parent_node = self.find_node_by_fullname(base_text)
|
||||
if not parent_node:
|
||||
# если base_text может содержать индекс типа foo[12], попробуем очистить
|
||||
base_text_clean = re.sub(r'\[\d+\]$', '', base_text)
|
||||
parent_node = self.find_node_by_fullname(base_text_clean)
|
||||
if parent_node:
|
||||
seen = set()
|
||||
for i in range(parent_node.childCount()):
|
||||
child = parent_node.child(i)
|
||||
cname = child.text(0)
|
||||
m = re.match(rf'^{re.escape(base_text)}\[(\d+)\]$', cname)
|
||||
if m and cname not in seen:
|
||||
completions.append(cname)
|
||||
seen.add(cname)
|
||||
self.completer.setModel(QStringListModel(completions))
|
||||
return completions
|
||||
|
||||
for item in items:
|
||||
# Берём последний фрагмент имени item, разделённого точками
|
||||
item_name_part = self.split_path(item.text(0))[-1].lower()
|
||||
|
||||
if item_name_part == part_lower:
|
||||
matched.append(item)
|
||||
|
||||
if not matched:
|
||||
return []
|
||||
items = []
|
||||
# Собираем детей для следующего уровня поиска
|
||||
for node in matched:
|
||||
for i in range(node.childCount()):
|
||||
items.append(node.child(i))
|
||||
|
||||
return matched
|
||||
|
||||
if not search_deep:
|
||||
# Без точки — ищем только в топ-уровне, фильтруя по prefix
|
||||
items = []
|
||||
if ends_with_sep:
|
||||
# Путь завершен, показываем детей узла
|
||||
node = self.find_node_by_fullname(text[:-1])
|
||||
if node:
|
||||
completions.extend(node.child(i).text(0) for i in range(node.childCount()))
|
||||
elif not path_parts:
|
||||
# Первый уровень — по вхождению
|
||||
for i in range(self.tree.topLevelItemCount()):
|
||||
item = self.tree.topLevelItem(i)
|
||||
name_part = self.split_path(item.text(0))[-1].lower()
|
||||
if name_part.startswith(prefix):
|
||||
items.append(item)
|
||||
completions = [item.text(0) for item in items]
|
||||
name = item.text(0).lower()
|
||||
if prefix in name:
|
||||
completions.append(item.text(0))
|
||||
else:
|
||||
# С точкой — углубляемся по пути и показываем имена детей
|
||||
if len(path_parts) == 0:
|
||||
items = [self.tree.topLevelItem(i) for i in range(self.tree.topLevelItemCount())]
|
||||
else:
|
||||
items = find_path_items(path_parts)
|
||||
node = find_exact_node(path_parts)
|
||||
if node:
|
||||
for i in range(node.childCount()):
|
||||
child = node.child(i)
|
||||
name = child.text(0)
|
||||
# Оптимизируем split_path - кэширование
|
||||
name_parts = child.data(0, Qt.UserRole + 10)
|
||||
if name_parts is None:
|
||||
name_parts = self.split_path(name)
|
||||
child.setData(0, Qt.UserRole + 10, name_parts)
|
||||
if not name_parts:
|
||||
continue
|
||||
last_part = name_parts[-1].lower()
|
||||
if prefix == '' or prefix in last_part: # здесь изменено
|
||||
completions.append(name)
|
||||
|
||||
completions = []
|
||||
for item in items:
|
||||
for i in range(item.childCount()):
|
||||
child = item.child(i)
|
||||
name_part = self.split_path(child.text(0))[-1].lower()
|
||||
if prefix == '' or name_part.startswith(prefix):
|
||||
completions.append(child.text(0))
|
||||
|
||||
|
||||
|
||||
self.completer.setModel(QStringListModel(completions))
|
||||
|
||||
self.completer.complete()
|
||||
return completions
|
||||
|
||||
|
||||
|
||||
def eventFilter(self, obj, event):
|
||||
if obj == self.search_input and isinstance(event, QKeyEvent):
|
||||
if event.key() == Qt.Key_Space and event.modifiers() & Qt.ControlModifier:
|
||||
completions = self.update_completions()
|
||||
self.completer.complete()
|
||||
text = self.search_input.text().strip()
|
||||
|
||||
if len(completions) == 1 and completions[0].lower() == text.lower():
|
||||
# Найдем узел с таким именем
|
||||
def find_exact_item(name):
|
||||
stack = [self.tree.topLevelItem(i) for i in range(self.tree.topLevelItemCount())]
|
||||
while stack:
|
||||
node = stack.pop()
|
||||
if node.text(0).lower() == name.lower():
|
||||
return node
|
||||
for i in range(node.childCount()):
|
||||
stack.append(node.child(i))
|
||||
return None
|
||||
|
||||
node = find_exact_item(completions[0])
|
||||
if node and node.childCount() > 0:
|
||||
# Используем первую подсказку, чтобы определить нужный разделитель
|
||||
completions = self.update_completions(text + '.')
|
||||
suggestion = completions[0]
|
||||
|
||||
# Ищем, какой символ идёт после текущего текста
|
||||
separator = '.'
|
||||
if suggestion.startswith(text):
|
||||
rest = suggestion[len(text):]
|
||||
if rest.startswith('->'):
|
||||
separator = '->'
|
||||
elif rest.startswith('.'):
|
||||
separator = '.'
|
||||
|
||||
self.search_input.setText(text + separator)
|
||||
completions = self.update_completions()
|
||||
self.completer.setModel(QStringListModel(completions))
|
||||
self.completer.complete()
|
||||
return True
|
||||
|
||||
# Иначе просто показываем подсказки
|
||||
self.completer.setModel(QStringListModel(completions))
|
||||
if completions:
|
||||
self.completer.complete()
|
||||
return True
|
||||
|
||||
return super().eventFilter(obj, event)
|
||||
|
||||
# Функция для поиска узла с полным именем
|
||||
def find_node_by_fullname(self, name):
|
||||
stack = [self.tree.topLevelItem(i) for i in range(self.tree.topLevelItemCount())]
|
||||
while stack:
|
||||
node = stack.pop()
|
||||
if node.text(0).lower() == name.lower():
|
||||
return node
|
||||
for i in range(node.childCount()):
|
||||
stack.append(node.child(i))
|
||||
return None
|
||||
return self.node_index.get(name.lower())
|
||||
|
||||
def insert_completion(self, text):
|
||||
|
||||
node = self.find_node_by_fullname(text)
|
||||
if node and node.childCount() > 0 and not (text.endswith('.') or text.endswith('->')):
|
||||
if node and node.childCount() > 0 and not (text.endswith('.') or text.endswith('->') or text.endswith('[')):
|
||||
# Определяем разделитель по имени первого ребёнка
|
||||
child_name = node.child(0).text(0)
|
||||
if child_name.startswith(text + '->'):
|
||||
text += '->'
|
||||
else:
|
||||
elif child_name.startswith(text + '.'):
|
||||
text += '.'
|
||||
elif '[' in child_name:
|
||||
text += '[' # для массивов
|
||||
else:
|
||||
text += '.' # fallback
|
||||
|
||||
if not self._bckspc_pressed:
|
||||
self.search_input.setText(text)
|
||||
self.search_input.setCursorPosition(len(text))
|
||||
|
||||
self.search_input.setText(text)
|
||||
self.search_input.setCursorPosition(len(text))
|
||||
self.update_completions()
|
||||
self.completer.complete()
|
||||
self.run_completions(text)
|
||||
else:
|
||||
self.search_input.setText(text)
|
||||
self.search_input.setCursorPosition(len(text))
|
||||
|
||||
def on_search_text_changed(self, text):
|
||||
if self.autocomplete_checkbox.isChecked():
|
||||
completions = self.update_completions(text)
|
||||
node = self.find_node_by_fullname(text)
|
||||
def eventFilter(self, obj, event):
|
||||
if obj == self.search_input and isinstance(event, QKeyEvent):
|
||||
if event.key() == Qt.Key_Space and event.modifiers() & Qt.ControlModifier:
|
||||
self.manual_completion_active = True
|
||||
text = self.search_input.text().strip()
|
||||
self.run_completions(text)
|
||||
elif event.key() == Qt.Key_Escape:
|
||||
# Esc — выключаем ручной режим и скрываем подсказки, если autocomplete выключен
|
||||
if not self.autocomplete_checkbox.isChecked():
|
||||
self.manual_completion_active = False
|
||||
self.completer.popup().hide()
|
||||
return True
|
||||
|
||||
should_show = False
|
||||
if event.key() == Qt.Key_Backspace:
|
||||
self._bckspc_pressed = True
|
||||
else:
|
||||
self._bckspc_pressed = False
|
||||
|
||||
if completions:
|
||||
if len(completions) > 1:
|
||||
should_show = True
|
||||
elif len(completions) == 1:
|
||||
single_node = self.find_node_by_fullname(completions[0])
|
||||
if single_node and single_node.childCount() > 0:
|
||||
should_show = True
|
||||
elif node and node.childCount() > 0 and not (text.endswith('.') or text.endswith('->')):
|
||||
should_show = True
|
||||
return super().eventFilter(obj, event)
|
||||
|
||||
def run_completions(self, text):
|
||||
completions = self.update_completions(text)
|
||||
|
||||
if should_show:
|
||||
if not self.autocomplete_checkbox.isChecked() and self._bckspc_pressed:
|
||||
text = text[:-1]
|
||||
|
||||
if len(completions) == 1 and completions[0].lower() == text.lower():
|
||||
# Найдем узел с таким именем
|
||||
def find_exact_item(name):
|
||||
stack = [self.tree.topLevelItem(i) for i in range(self.tree.topLevelItemCount())]
|
||||
while stack:
|
||||
node = stack.pop()
|
||||
if node.text(0).lower() == name.lower():
|
||||
return node
|
||||
for i in range(node.childCount()):
|
||||
stack.append(node.child(i))
|
||||
return None
|
||||
|
||||
node = find_exact_item(completions[0])
|
||||
if node and node.childCount() > 0:
|
||||
# Используем первую подсказку, чтобы определить нужный разделитель
|
||||
completions = self.update_completions(text + '.')
|
||||
suggestion = completions[0]
|
||||
|
||||
# Ищем, какой символ идёт после текущего текста
|
||||
separator = '.'
|
||||
if suggestion.startswith(text):
|
||||
rest = suggestion[len(text):]
|
||||
if rest.startswith(text + '->'):
|
||||
separator += '->'
|
||||
elif rest.startswith(text + '.'):
|
||||
separator += '.'
|
||||
elif '[' in rest:
|
||||
separator += '[' # для массивов
|
||||
else:
|
||||
separator += '.' # fallback
|
||||
|
||||
if not self._bckspc_pressed:
|
||||
self.search_input.setText(text + separator)
|
||||
completions = self.update_completions(text)
|
||||
self.completer.setModel(QStringListModel(completions))
|
||||
self.completer.complete()
|
||||
return True
|
||||
|
||||
# Иначе просто показываем подсказки
|
||||
self.completer.setModel(QStringListModel(completions))
|
||||
if completions:
|
||||
self.completer.complete()
|
||||
return True
|
||||
|
||||
def on_search_text_changed(self, text):
|
||||
self.filter_tree()
|
||||
if text == None:
|
||||
text = self.search_input.text().strip()
|
||||
if self.autocomplete_checkbox.isChecked():
|
||||
self.run_completions(text)
|
||||
else:
|
||||
# Если выключено, показываем подсказки только если флаг ручного вызова True
|
||||
if self.manual_completion_active:
|
||||
self.run_completions(text)
|
||||
else:
|
||||
self.completer.popup().hide()
|
||||
|
||||
def on_add_clicked(self):
|
||||
self.selected_names = []
|
||||
@ -447,9 +482,7 @@ class VariableSelectorDialog(QDialog):
|
||||
QMessageBox.warning(self, "Ошибка", "Путь к XML не задан, невозможно обновить переменные.")
|
||||
return
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
tree = ET.parse(self.xml_path)
|
||||
root = tree.getroot()
|
||||
root, tree = myXML.safe_parse_xml(self.xml_path)
|
||||
if root is None:
|
||||
return
|
||||
|
||||
@ -470,7 +503,6 @@ class VariableSelectorDialog(QDialog):
|
||||
|
||||
myXML.fwrite(root, self.xml_path)
|
||||
|
||||
self.populate_tree()
|
||||
self.done(QDialog.Accepted)
|
||||
|
||||
|
||||
@ -488,8 +520,50 @@ class VariableSelectorDialog(QDialog):
|
||||
def delete_selected_vars(self):
|
||||
selected_names = self._get_selected_var_names()
|
||||
if not selected_names:
|
||||
print("nothing selected")
|
||||
return
|
||||
|
||||
# Обновляем var_map и all_vars
|
||||
for name in selected_names:
|
||||
if name in self.var_map:
|
||||
self.var_map[name]['show_var'] = 'false'
|
||||
self.var_map[name]['enable'] = 'false'
|
||||
|
||||
for v in self.all_vars:
|
||||
if v['name'] == name:
|
||||
v['show_var'] = 'false'
|
||||
v['enable'] = 'false'
|
||||
break
|
||||
|
||||
# Проверка пути к XML
|
||||
if not hasattr(self, 'xml_path') or not self.xml_path:
|
||||
from PySide2.QtWidgets import QMessageBox
|
||||
QMessageBox.warning(self, "Ошибка", "Путь к XML не задан, невозможно обновить переменные.")
|
||||
return
|
||||
|
||||
root, tree = myXML.safe_parse_xml(self.xml_path)
|
||||
if root is None:
|
||||
return
|
||||
|
||||
vars_section = root.find('variables')
|
||||
if vars_section is None:
|
||||
return
|
||||
|
||||
for var_elem in vars_section.findall('var'):
|
||||
name = var_elem.attrib.get('name')
|
||||
if name in selected_names:
|
||||
def set_text(tag, value):
|
||||
el = var_elem.find(tag)
|
||||
if el is None:
|
||||
el = ET.SubElement(var_elem, tag)
|
||||
el.text = value
|
||||
set_text('show_var', 'false')
|
||||
set_text('enable', 'false')
|
||||
|
||||
myXML.fwrite(root, self.xml_path)
|
||||
|
||||
self.table.populate(self.all_vars, self.structs, None)
|
||||
|
||||
# Проверка пути к XML
|
||||
if not hasattr(self, 'xml_path') or not self.xml_path:
|
||||
from PySide2.QtWidgets import QMessageBox
|
||||
@ -497,8 +571,7 @@ class VariableSelectorDialog(QDialog):
|
||||
return
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
tree = ET.parse(self.xml_path)
|
||||
root = tree.getroot()
|
||||
root, tree = myXML.safe_parse_xml(self.xml_path)
|
||||
if root is None:
|
||||
return
|
||||
|
||||
@ -517,10 +590,22 @@ class VariableSelectorDialog(QDialog):
|
||||
# Удаляем из all_vars (глобально)
|
||||
self.all_vars[:] = [v for v in self.all_vars if v['name'] not in selected_names]
|
||||
|
||||
# Удаляем из expanded_vars (тоже глобально)
|
||||
def filter_out_selected(vars_list):
|
||||
filtered = []
|
||||
for v in vars_list:
|
||||
if v['name'] not in selected_names:
|
||||
# Рекурсивно фильтруем детей, если есть
|
||||
if 'children' in v:
|
||||
v = v.copy()
|
||||
v['children'] = filter_out_selected(v['children'])
|
||||
filtered.append(v)
|
||||
return filtered
|
||||
|
||||
self.expanded_vars[:] = filter_out_selected(self.expanded_vars)
|
||||
if removed_any:
|
||||
myXML.fwrite(root, self.xml_path)
|
||||
|
||||
self.populate_tree()
|
||||
self.filter_tree()
|
||||
|
||||
def _get_selected_var_names(self):
|
||||
@ -532,6 +617,82 @@ class VariableSelectorDialog(QDialog):
|
||||
self.settings.setValue("autocomplete_enabled", self.autocomplete_checkbox.isChecked())
|
||||
|
||||
|
||||
|
||||
def split_path(self, path):
|
||||
# Разбиваем по точке или по -> (учитываем, что -> длиной 2 символа)
|
||||
return re.split(r'\.|->', path)
|
||||
"""
|
||||
Разбивает путь на компоненты:
|
||||
- 'foo[2].bar[1]->baz' → ['foo', [2]', 'bar', '[1]' 'baz']
|
||||
"""
|
||||
tokens = []
|
||||
token = ''
|
||||
i = 0
|
||||
while i < len(path):
|
||||
c = path[i]
|
||||
# Разделители: '->' и '.'
|
||||
if c == '-' and path[i:i+2] == '->':
|
||||
if token:
|
||||
tokens.append(token)
|
||||
token = ''
|
||||
i += 2
|
||||
continue
|
||||
elif c == '.':
|
||||
if token:
|
||||
tokens.append(token)
|
||||
token = ''
|
||||
i += 1
|
||||
continue
|
||||
elif c == '[':
|
||||
# Заканчиваем текущий токен, если есть
|
||||
if token:
|
||||
tokens.append(token)
|
||||
token = ''
|
||||
# Собираем индекс [N]
|
||||
idx = ''
|
||||
while i < len(path) and path[i] != ']':
|
||||
idx += path[i]
|
||||
i += 1
|
||||
if i < len(path) and path[i] == ']':
|
||||
idx += ']'
|
||||
i += 1
|
||||
tokens.append(idx)
|
||||
continue
|
||||
else:
|
||||
token += c
|
||||
i += 1
|
||||
if token:
|
||||
tokens.append(token)
|
||||
return tokens
|
||||
|
||||
|
||||
def filter_vars(vars_list, path_parts):
|
||||
"""Рекурсивно фильтруем vars_list по path_parts и возвращаем только подходящие."""
|
||||
filtered = []
|
||||
|
||||
def matches_path(name, search_parts):
|
||||
name_parts = name.lower().split('.')
|
||||
if len(name_parts) < len(search_parts):
|
||||
return False
|
||||
for sp, np in zip(search_parts, name_parts):
|
||||
if not np.startswith(sp):
|
||||
return False
|
||||
return True
|
||||
|
||||
for var in vars_list:
|
||||
fullname = var.get('fullname', var['name']) # желательно иметь полное имя
|
||||
# Если фильтра нет — берем всё
|
||||
if not path_parts or matches_path(fullname, path_parts):
|
||||
# Копируем узел с рекурсией по детям
|
||||
new_var = var.copy()
|
||||
if 'children' in var:
|
||||
new_var['children'] = filter_vars(var['children'], path_parts)
|
||||
filtered.append(new_var)
|
||||
else:
|
||||
# Но даже если этот узел не подходит, может подойти его потомок
|
||||
if 'children' in var:
|
||||
child_filtered = filter_vars(var['children'], path_parts)
|
||||
if child_filtered:
|
||||
new_var = var.copy()
|
||||
new_var['children'] = child_filtered
|
||||
filtered.append(new_var)
|
||||
|
||||
return filtered
|
||||
|
@ -1,6 +1,12 @@
|
||||
|
||||
import sys
|
||||
import re
|
||||
import multiprocessing
|
||||
import sys
|
||||
import contextlib
|
||||
import io
|
||||
import json
|
||||
from scanVars import run_scan
|
||||
from VariableTable import VariableTableWidget, rows
|
||||
|
||||
from PySide2.QtWidgets import (
|
||||
@ -10,7 +16,7 @@ from PySide2.QtWidgets import (
|
||||
QDialog, QTreeWidget, QTreeWidgetItem, QSizePolicy, QHeaderView, QProgressBar
|
||||
)
|
||||
from PySide2.QtGui import QTextCursor, QKeyEvent
|
||||
from PySide2.QtCore import Qt, QProcess, QObject, Signal, QSettings
|
||||
from PySide2.QtCore import Qt, QProcess, QObject, Signal, QTimer
|
||||
|
||||
|
||||
class EmittingStream(QObject):
|
||||
@ -67,24 +73,28 @@ class EmittingStream(QObject):
|
||||
self._buffer = ""
|
||||
|
||||
|
||||
class ProcessOutputWindowDummy(QDialog):
|
||||
def __init__(self, on_done_callback):
|
||||
|
||||
class ProcessOutputWindow(QDialog):
|
||||
def __init__(self, proj_path, makefile_path, xml_path, on_done_callback=None):
|
||||
super().__init__()
|
||||
self.setWindowTitle("Поиск переменных...")
|
||||
self.resize(600, 480)
|
||||
self.setModal(True)
|
||||
|
||||
self.proj_path = proj_path
|
||||
self.makefile_path = makefile_path
|
||||
self.xml_path = xml_path
|
||||
self._on_done_callback = on_done_callback
|
||||
|
||||
self.layout = QVBoxLayout(self)
|
||||
|
||||
self.output_edit = QTextEdit()
|
||||
self.output_edit.setReadOnly(True)
|
||||
self.layout.addWidget(self.output_edit)
|
||||
|
||||
# Метка с именем прогрессбара
|
||||
self.progress_label = QLabel("Progress:")
|
||||
self.layout.addWidget(self.progress_label)
|
||||
|
||||
# Прогрессбар
|
||||
self.progress_bar = QProgressBar()
|
||||
self.progress_bar.setMinimum(0)
|
||||
self.progress_bar.setValue(0)
|
||||
@ -93,33 +103,117 @@ class ProcessOutputWindowDummy(QDialog):
|
||||
self.btn_close = QPushButton("Закрыть")
|
||||
self.btn_close.setEnabled(False)
|
||||
self.layout.addWidget(self.btn_close)
|
||||
|
||||
self.btn_close.clicked.connect(self.__handle_done)
|
||||
self._on_done_callback = on_done_callback
|
||||
|
||||
self.emitting_stream = EmittingStream()
|
||||
self.emitting_stream.text_written.connect(self.append_text)
|
||||
self.emitting_stream.progress_updated.connect(self.update_progress)
|
||||
self.queue = None
|
||||
self.proc = None
|
||||
|
||||
sys.stdout = self.emitting_stream
|
||||
|
||||
def __handle_done(self):
|
||||
sys.stdout = sys.__stdout__ # восстановить stdout
|
||||
if self._on_done_callback:
|
||||
self._on_done_callback()
|
||||
self.accept()
|
||||
def start_scan(self):
|
||||
self.queue = multiprocessing.Queue()
|
||||
self.proc = multiprocessing.Process(
|
||||
target=run_scan_process,
|
||||
args=(self.proj_path, self.makefile_path, self.xml_path, self.queue),
|
||||
daemon=True)
|
||||
self.proc.start()
|
||||
|
||||
self.timer = QTimer(self)
|
||||
self.timer.timeout.connect(self.poll_queue)
|
||||
self.timer.start(100)
|
||||
|
||||
self.show()
|
||||
|
||||
def poll_queue(self):
|
||||
try:
|
||||
while True:
|
||||
msg = self.queue.get_nowait()
|
||||
if msg is None:
|
||||
# Конец процесса
|
||||
self.btn_close.setEnabled(True)
|
||||
self.append_text("\n--- Анализ завершён ---")
|
||||
self.timer.stop()
|
||||
return
|
||||
# Пытаемся разобрать JSON-сообщение
|
||||
if isinstance(msg, str) and msg.startswith("PROGRESS_MSG:"):
|
||||
try:
|
||||
data = json.loads(msg[len("PROGRESS_MSG:"):])
|
||||
self.update_progress(data["bar_name"], data["current"], data["total"])
|
||||
except Exception:
|
||||
# Если не удалось распарсить, выводим как текст
|
||||
self.append_text(msg)
|
||||
else:
|
||||
self.append_text(msg)
|
||||
except Exception:
|
||||
pass # Очередь пустая
|
||||
|
||||
def append_text(self, text):
|
||||
cursor = self.output_edit.textCursor()
|
||||
cursor.movePosition(QTextCursor.End)
|
||||
if not text.endswith('\n'):
|
||||
text += '\n'
|
||||
for line in text.splitlines(True):
|
||||
cursor.insertText(line)
|
||||
cursor.insertText(text)
|
||||
self.output_edit.setTextCursor(cursor)
|
||||
self.output_edit.ensureCursorVisible()
|
||||
|
||||
def update_progress(self, bar_name, current, total):
|
||||
self.progress_label.setText(f"{bar_name}")
|
||||
self.progress_bar.setMaximum(total)
|
||||
self.progress_bar.setValue(current)
|
||||
self.progress_bar.setValue(current)
|
||||
|
||||
def __handle_done(self):
|
||||
self.close()
|
||||
|
||||
def closeEvent(self, event):
|
||||
if self.proc and self.proc.is_alive():
|
||||
self.proc.terminate()
|
||||
self.proc.join()
|
||||
self.btn_close.setEnabled(True)
|
||||
self.append_text("Сканирование прервано.")
|
||||
|
||||
|
||||
def run_scan_process(proj_path, makefile_path, xml_path, queue):
|
||||
class QueueWriter(io.TextIOBase):
|
||||
def __init__(self):
|
||||
self._buffer = ""
|
||||
self._current_bar_name = None
|
||||
|
||||
def write(self, txt):
|
||||
self._buffer += txt
|
||||
while '\n' in self._buffer:
|
||||
line, self._buffer = self._buffer.split('\n', 1)
|
||||
# Обработка прогресса
|
||||
if line.startswith('Progress: "') and line.endswith('"'):
|
||||
# Название прогресс-бара
|
||||
bar_name = line[len('Progress: "'):-1]
|
||||
self._current_bar_name = bar_name
|
||||
elif re.match(r'^Progress:\s*\d+\s*/\s*\d+$', line):
|
||||
m = re.match(r'^Progress:\s*(\d+)\s*/\s*(\d+)$', line)
|
||||
if m:
|
||||
current = int(m.group(1))
|
||||
total = int(m.group(2))
|
||||
bar_name = self._current_bar_name or "Progress"
|
||||
# Отправляем специальное сообщение в очередь в формате JSON
|
||||
msg = {
|
||||
"bar_name": bar_name,
|
||||
"current": current,
|
||||
"total": total
|
||||
}
|
||||
queue.put("PROGRESS_MSG:" + json.dumps(msg))
|
||||
else:
|
||||
# Обычный вывод
|
||||
queue.put(line)
|
||||
|
||||
def flush(self):
|
||||
if self._buffer.strip():
|
||||
queue.put(self._buffer.strip())
|
||||
self._buffer = ""
|
||||
|
||||
sys.stdout = QueueWriter()
|
||||
sys.stderr = sys.stdout
|
||||
|
||||
try:
|
||||
run_scan(proj_path, makefile_path, xml_path)
|
||||
except Exception as e:
|
||||
queue.put(f"[ОШИБКА] {e}")
|
||||
finally:
|
||||
queue.put(None) # сигнал окончания
|
@ -14,7 +14,6 @@ from xml.dom import minidom
|
||||
from parseMakefile import parse_makefile
|
||||
from collections import deque
|
||||
import argparse
|
||||
from setupVars import make_relative_path
|
||||
import myXML
|
||||
BITFIELD_WIDTHS = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}
|
||||
|
||||
@ -582,12 +581,12 @@ def generate_xml_output(proj_path, xml_path, unique_vars, h_files_needed, vars_n
|
||||
}
|
||||
|
||||
if makefile_path:
|
||||
rel_makefile = make_relative_path(makefile_path, proj_path)
|
||||
rel_makefile = myXML.make_relative_path(makefile_path, proj_path)
|
||||
if not os.path.isabs(rel_makefile):
|
||||
analysis_attrs["makefile_path"] = rel_makefile.replace("\\", "/")
|
||||
|
||||
if structs_xml_path:
|
||||
rel_struct = make_relative_path(structs_xml_path, proj_path)
|
||||
rel_struct = myXML.make_relative_path(structs_xml_path, proj_path)
|
||||
if not os.path.isabs(rel_struct):
|
||||
analysis_attrs["structs_path"] = rel_struct.replace("\\", "/")
|
||||
|
||||
@ -919,6 +918,5 @@ def run_scan(proj_path, makefile_path, output_xml, verbose=2):
|
||||
|
||||
print("[XML] Creating vars.xml...")
|
||||
generate_xml_output(proj_path, output_xml, vars, includes, externs, structs_xml, makefile_path)
|
||||
print("Progress: 2/2")
|
||||
print('Progress: "Done"')
|
||||
print('Progress: 1/1')
|
||||
print("Progress: 2/2")
|
||||
|
207
Src/setupVars.py
207
Src/setupVars.py
@ -153,37 +153,195 @@ def parse_structs(filename):
|
||||
|
||||
|
||||
|
||||
def parse_array_dims(type_str):
|
||||
"""Возвращает базовый тип и список размеров массива"""
|
||||
dims = list(map(int, re.findall(r'\[(\d+)\]', type_str)))
|
||||
base_type = re.sub(r'\[\d+\]', '', type_str).strip()
|
||||
return base_type, dims
|
||||
|
||||
def generate_array_names(prefix, dims, depth=0):
|
||||
"""Рекурсивно генерирует имена для всех элементов многомерного массива"""
|
||||
if not dims:
|
||||
return [prefix]
|
||||
|
||||
result = []
|
||||
for i in range(dims[0]):
|
||||
new_prefix = f"{prefix}[{i}]"
|
||||
children = generate_array_names(new_prefix, dims[1:], depth + 1)
|
||||
result.append({
|
||||
'name': new_prefix,
|
||||
'children': children if len(dims) > 1 else None
|
||||
})
|
||||
return result
|
||||
|
||||
def flatten_array_tree(array_tree):
|
||||
"""Разворачивает дерево массивов в линейный список с вложенными children"""
|
||||
result = []
|
||||
for node in array_tree:
|
||||
entry = {'name': node['name']}
|
||||
if node['children']:
|
||||
entry['children'] = flatten_array_tree(node['children'])
|
||||
result.append(entry)
|
||||
return result
|
||||
|
||||
|
||||
def expand_struct_recursively(prefix, type_str, structs, typedefs, var_attrs, depth=0):
|
||||
if depth > 10:
|
||||
return []
|
||||
|
||||
# Если type_str — словарь структуры
|
||||
# Вспомогательная функция для обработки массивов
|
||||
def process_array(prefix, type_str, structs, typedefs, var_attrs, depth=0):
|
||||
base_type, array_dims = parse_array_dims(type_str)
|
||||
if not array_dims:
|
||||
return []
|
||||
|
||||
# На текущем уровне берем первый размер массива
|
||||
current_dim = array_dims[0]
|
||||
# Оставшиеся размеры — все, кроме первого
|
||||
remaining_dims = array_dims[1:]
|
||||
|
||||
# Для создания типа с оставшимися размерами:
|
||||
if remaining_dims:
|
||||
# Формируем строку типа для оставшихся измерений массива, например int[16]
|
||||
remaining_type_str = f"{base_type}{''.join(f'[{d}]' for d in remaining_dims)}"
|
||||
else:
|
||||
remaining_type_str = base_type
|
||||
|
||||
array_tree = []
|
||||
for i in range(current_dim):
|
||||
name = f"{prefix}[{i}]"
|
||||
# Для каждого элемента передаем уже оставшийся тип массива
|
||||
children = expand_struct_recursively(name, remaining_type_str, structs, typedefs, var_attrs, depth + 1)
|
||||
node = {
|
||||
'name': name,
|
||||
'type': remaining_type_str if remaining_dims else base_type,
|
||||
'pt_type': '',
|
||||
'iq_type': '',
|
||||
'return_type': '',
|
||||
'file': var_attrs.get('file'),
|
||||
'extern': var_attrs.get('extern'),
|
||||
'static': var_attrs.get('static'),
|
||||
}
|
||||
if children:
|
||||
node['children'] = children
|
||||
array_tree.append(node)
|
||||
|
||||
return array_tree
|
||||
|
||||
|
||||
# Если type_str — уже распарсенная структура (dict)
|
||||
if isinstance(type_str, dict):
|
||||
fields = type_str
|
||||
else:
|
||||
# Проверяем, массив ли это
|
||||
base_type, array_dims = parse_array_dims(type_str)
|
||||
if array_dims:
|
||||
return process_array(prefix, type_str, structs, typedefs, var_attrs, depth)
|
||||
|
||||
# Ищем структуру по имени типа
|
||||
base_type = scanVars.strip_ptr_and_array(type_str)
|
||||
fields = structs.get(base_type)
|
||||
if not isinstance(fields, dict):
|
||||
# Не структура и не массив — просто возвращаем пустой список
|
||||
return []
|
||||
|
||||
children = []
|
||||
for field_name, field_value in fields.items():
|
||||
# Пропускаем поле 'type', оно служит для хранения имени типа
|
||||
if field_name == 'type':
|
||||
continue
|
||||
|
||||
# Определяем разделитель между prefix и полем
|
||||
# Формируем полное имя поля
|
||||
if prefix.endswith('*'):
|
||||
separator = '->'
|
||||
# Для красоты можно убрать пробелы у указателя
|
||||
# например, если prefix="ptr*" -> "ptr->field"
|
||||
full_name = f"{prefix[:-1]}{separator}{field_name}"
|
||||
else:
|
||||
separator = '.'
|
||||
full_name = f"{prefix}{separator}{field_name}"
|
||||
|
||||
# Определяем тип поля
|
||||
if isinstance(field_value, dict) and isinstance(field_value.get('type'), str):
|
||||
field_type_str = field_value['type']
|
||||
elif isinstance(field_value, str):
|
||||
field_type_str = field_value
|
||||
else:
|
||||
field_type_str = None
|
||||
|
||||
# Обработка, если поле — строка (тип или массив)
|
||||
if field_type_str:
|
||||
base_subtype, sub_dims = parse_array_dims(field_type_str)
|
||||
if sub_dims:
|
||||
# Массив — раскрываем элементы
|
||||
array_parent = {
|
||||
'name': full_name,
|
||||
'type': field_type_str,
|
||||
'pt_type': '',
|
||||
'iq_type': '',
|
||||
'return_type': '',
|
||||
'file': var_attrs.get('file'),
|
||||
'extern': var_attrs.get('extern'),
|
||||
'static': var_attrs.get('static'),
|
||||
}
|
||||
|
||||
array_children = []
|
||||
flat_names = generate_array_names(full_name, sub_dims)
|
||||
for node in flat_names:
|
||||
# node — dict с ключом 'name' и (возможно) 'children'
|
||||
sub_items = expand_struct_recursively(node['name'], base_subtype, structs, typedefs, var_attrs, depth + 1)
|
||||
child_node = {
|
||||
'name': node['name'],
|
||||
'type': base_subtype,
|
||||
'pt_type': '',
|
||||
'iq_type': '',
|
||||
'return_type': '',
|
||||
'file': var_attrs.get('file'),
|
||||
'extern': var_attrs.get('extern'),
|
||||
'static': var_attrs.get('static'),
|
||||
}
|
||||
if sub_items:
|
||||
child_node['children'] = sub_items
|
||||
array_children.append(child_node)
|
||||
|
||||
array_parent['children'] = array_children
|
||||
children.append(array_parent)
|
||||
continue
|
||||
|
||||
# Игнорируем указатели на функции
|
||||
if "(" in field_type_str and "*" in field_type_str and ")" in field_type_str:
|
||||
continue
|
||||
|
||||
if isinstance(field_value, dict):
|
||||
# Это одиночная структура — раскрываем рекурсивно
|
||||
sub_items = expand_struct_recursively(full_name, field_value, structs, typedefs, var_attrs, depth + 1)
|
||||
child = {
|
||||
'name': full_name,
|
||||
'type': field_type_str,
|
||||
'pt_type': '',
|
||||
'iq_type': '',
|
||||
'return_type': '',
|
||||
'file': var_attrs.get('file'),
|
||||
'extern': var_attrs.get('extern'),
|
||||
'static': var_attrs.get('static'),
|
||||
}
|
||||
if sub_items:
|
||||
child['children'] = sub_items
|
||||
children.append(child)
|
||||
else:
|
||||
# Обычное поле (int, float, etc.)
|
||||
child = {
|
||||
'name': full_name,
|
||||
'type': field_type_str,
|
||||
'pt_type': '',
|
||||
'iq_type': '',
|
||||
'return_type': '',
|
||||
'file': var_attrs.get('file'),
|
||||
'extern': var_attrs.get('extern'),
|
||||
'static': var_attrs.get('static'),
|
||||
}
|
||||
children.append(child)
|
||||
continue
|
||||
|
||||
# Если поле — dict без 'type' или со сложной структурой, обрабатываем как вложенную структуру
|
||||
if isinstance(field_value, dict):
|
||||
# Если вложенная структура — берем её имя типа из поля 'type' или пустую строку
|
||||
type_name = field_value.get('type', '')
|
||||
child = {
|
||||
'name': full_name,
|
||||
@ -195,35 +353,14 @@ def expand_struct_recursively(prefix, type_str, structs, typedefs, var_attrs, de
|
||||
'extern': var_attrs.get('extern'),
|
||||
'static': var_attrs.get('static'),
|
||||
}
|
||||
if '*' in type_name and not full_name.endswith('*'):
|
||||
full_name += '*'
|
||||
# Рекурсивно раскрываем вложенные поля
|
||||
subchildren = expand_struct_recursively(full_name, field_value, structs, typedefs, var_attrs, depth + 1)
|
||||
if subchildren:
|
||||
child['children'] = subchildren
|
||||
else:
|
||||
# Простое поле — строка типа
|
||||
# Пропускаем указатели на функции
|
||||
if isinstance(field_value, str) and "(" in field_value and "*" in field_value and ")" in field_value:
|
||||
continue
|
||||
|
||||
child = {
|
||||
'name': full_name,
|
||||
'type': field_value,
|
||||
'pt_type': '',
|
||||
'iq_type': '',
|
||||
'return_type': '',
|
||||
'file': var_attrs.get('file'),
|
||||
'extern': var_attrs.get('extern'),
|
||||
'static': var_attrs.get('static'),
|
||||
}
|
||||
|
||||
children.append(child)
|
||||
children.append(child)
|
||||
|
||||
return children
|
||||
|
||||
|
||||
|
||||
def expand_vars(vars_list, structs, typedefs):
|
||||
"""
|
||||
Раскрывает структуры и массивы структур в деревья.
|
||||
@ -233,26 +370,26 @@ def expand_vars(vars_list, structs, typedefs):
|
||||
for var in vars_list:
|
||||
pt_type = var.get('pt_type', '')
|
||||
raw_type = var.get('type', '')
|
||||
base_type = scanVars.strip_ptr_and_array(raw_type)
|
||||
|
||||
fields = structs.get(base_type)
|
||||
|
||||
if pt_type.startswith('pt_ptr_') and isinstance(fields, dict):
|
||||
if var['name'] == 'project':
|
||||
a = 1
|
||||
|
||||
if pt_type.startswith('pt_ptr_'):
|
||||
new_var = var.copy()
|
||||
new_var['children'] = expand_struct_recursively(var['name']+'*', raw_type, structs, typedefs, var)
|
||||
expanded.append(new_var)
|
||||
|
||||
if pt_type.startswith('pt_arr_') and isinstance(fields, dict):
|
||||
if pt_type.startswith('pt_arr_'):
|
||||
new_var = var.copy()
|
||||
new_var['children'] = expand_struct_recursively(var['name'], raw_type, structs, typedefs, var)
|
||||
expanded.append(new_var)
|
||||
|
||||
elif pt_type == 'pt_struct' and isinstance(fields, dict):
|
||||
elif pt_type == 'pt_struct':
|
||||
new_var = var.copy()
|
||||
new_var['children'] = expand_struct_recursively(var['name'], raw_type, structs, typedefs, var)
|
||||
expanded.append(new_var)
|
||||
|
||||
elif pt_type == 'pt_union' and isinstance(fields, dict):
|
||||
elif pt_type == 'pt_union':
|
||||
new_var = var.copy()
|
||||
new_var['children'] = expand_struct_recursively(var['name'], raw_type, structs, typedefs, var)
|
||||
expanded.append(new_var)
|
||||
|
186
debug_tools.c
186
debug_tools.c
@ -2,28 +2,133 @@
|
||||
#include "IQmathLib.h"
|
||||
|
||||
static int getDebugVar(DebugVar_t *var, long *int_var, float *float_var);
|
||||
static int convertDebugVarToIQx(DebugVar_t *var, long *ret_var, DebugVarIQType_t iq_type_final);
|
||||
static int convertDebugVarToIQx(DebugVar_t *var, long *ret_var);
|
||||
|
||||
DebugVarIQType_t dbg_type = t_iq24;
|
||||
|
||||
long Debug_ReadVar(DebugVar_t *var, DebugVarIQType_t iq_type_final)
|
||||
|
||||
|
||||
int Debug_LowLevel_ReadVar(DebugVar_t *var_ll, long *return_long)
|
||||
{
|
||||
long tmp_var;
|
||||
if((var->ptr_type == pt_struct) || (var->ptr_type == pt_union) || (var->ptr_type == pt_unknown))
|
||||
return;
|
||||
if (var_ll == NULL)
|
||||
return 1;
|
||||
|
||||
convertDebugVarToIQx(var, &tmp_var, dbg_type);
|
||||
char *addr = var_ll->Ptr;
|
||||
unsigned long addr_val = (unsigned long)addr;
|
||||
// Ðàçðåø¸ííûå äèàïàçîíû ïàìÿòè íà TMS320F2812
|
||||
if (!(
|
||||
(addr_val <= 0x0007FF) || // RAMM0 + RAMM1
|
||||
(addr_val >= 0x008000 && addr_val <= 0x009FFF) || // L0 + L1 SARAM
|
||||
(addr_val >= 0x3F8000 && addr_val <= 0x3F9FFF) || // PRAMH0 + DRAMH0
|
||||
(addr_val >= 0x3D8000 && addr_val <= 0x3EFFFF) || // Flash A-F
|
||||
(addr_val >= 0x3FF000 && addr_val <= 0x3FFFFF) // Boot ROM / Reset
|
||||
)) {
|
||||
return 2; // àäðåñ âíå äîïóñòèìîãî äèàïàçîíà, èãíîðèðóåì
|
||||
}
|
||||
|
||||
return tmp_var;
|
||||
convertDebugVarToIQx(var_ll, return_long);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int convertDebugVarToIQx(DebugVar_t *var, long *ret_var, DebugVarIQType_t iq_type_final)
|
||||
|
||||
int Debug_ReadVar(DebugVar_t *var, long *return_long)
|
||||
{
|
||||
long tmp_var;
|
||||
if (var == NULL)
|
||||
return 1;
|
||||
if((var->ptr_type == pt_struct) || (var->ptr_type == pt_union) ||
|
||||
(var->ptr_type == pt_unknown) || (var->return_type == pt_unknown))
|
||||
return 1;
|
||||
|
||||
convertDebugVarToIQx(var, return_long);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Debug_ReadVarName(DebugVar_t *var, char *name_ptr)
|
||||
{
|
||||
if((var == NULL)||(name_ptr == NULL))
|
||||
return 1;
|
||||
int i;
|
||||
// Êîïèðîâàíèå ñ çàùèòîé îò ïåðåïîëíåíèÿ è ÿâíîé îñòàíîâêîé ïî '\0'
|
||||
for (i = 0; i < sizeof(var->name); i++)
|
||||
{
|
||||
name_ptr[i] = var->name[i];
|
||||
if (var->name[i] == '\0')
|
||||
break;
|
||||
}
|
||||
// Ãàðàíòèðîâàííîå çàâåðøåíèå ñòðîêè (íà ñëó÷àé, åñëè â var->name íå áûëî '\0')
|
||||
name_ptr[sizeof(var->name) - 1] = '\0';
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
int Debug_LowLevel_Initialize(const uint8_t* external_date)
|
||||
{
|
||||
if (external_date == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Ïðåîáðàçóåì external_date â ñòðóêòóðó
|
||||
DateTimeHex ext;
|
||||
ext.year = (external_date[0] << 8) | external_date[1];
|
||||
ext.month = external_date[2];
|
||||
ext.day = external_date[3];
|
||||
ext.hour = external_date[4];
|
||||
ext.minute = external_date[5];
|
||||
|
||||
// Ïàðñèì BUILD_FULL_DATE "YYYYMMDD_HHMM"
|
||||
DateTimeHex build;
|
||||
char buf[5] = {0};
|
||||
|
||||
// Ãîä
|
||||
memcpy(buf, BUILD_FULL_DATE + 0, 4);
|
||||
build.year = (uint16_t)atoi(buf);
|
||||
|
||||
// Ìåñÿö
|
||||
memcpy(buf, BUILD_FULL_DATE + 4, 2);
|
||||
build.month = (uint8_t)atoi(buf);
|
||||
|
||||
// Äåíü
|
||||
memcpy(buf, BUILD_FULL_DATE + 6, 2);
|
||||
build.day = (uint8_t)atoi(buf);
|
||||
|
||||
// ×àñ
|
||||
memcpy(buf, BUILD_FULL_DATE + 9, 2);
|
||||
build.hour = (uint8_t)atoi(buf);
|
||||
|
||||
// Ìèíóòû
|
||||
memcpy(buf, BUILD_FULL_DATE + 11, 2);
|
||||
build.minute = (uint8_t)atoi(buf);
|
||||
|
||||
// Ñðàâíåíèå âñåõ ïîëåé
|
||||
if (ext.year == build.year &&
|
||||
ext.month == build.month &&
|
||||
ext.day == build.day &&
|
||||
ext.hour == build.hour &&
|
||||
ext.minute == build.minute)
|
||||
{
|
||||
return 0; // Ñîâïàëî
|
||||
}
|
||||
|
||||
return 1; // Íå ñîâïàëî
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/////////////////////----INTERNAL FUNCTIONS-----////////////////////////
|
||||
static int convertDebugVarToIQx(DebugVar_t *var, long *ret_var)
|
||||
{
|
||||
long iq_numb, iq_united, iq_final;
|
||||
float float_numb;
|
||||
|
||||
if(getDebugVar(var, &iq_numb, &float_numb) == 1)
|
||||
return 1;
|
||||
if(getDebugVar(var, &iq_numb, &float_numb) != 0)
|
||||
return 1;
|
||||
|
||||
// ïðèâåäåíèå ê îäíîìó IQ
|
||||
switch(var->iq_type)
|
||||
@ -131,7 +236,7 @@ static int convertDebugVarToIQx(DebugVar_t *var, long *ret_var, DebugVarIQType_t
|
||||
}
|
||||
|
||||
// ïðèâåäåíèå îáùåãî IQ ê çàïðàøèâàåìîìó
|
||||
switch(iq_type_final)
|
||||
switch(var->return_type)
|
||||
{
|
||||
case t_iq_none:
|
||||
iq_final = (int)_IQtoF(iq_united);
|
||||
@ -233,42 +338,53 @@ static int convertDebugVarToIQx(DebugVar_t *var, long *ret_var, DebugVarIQType_t
|
||||
}
|
||||
|
||||
|
||||
|
||||
static int getDebugVar(DebugVar_t *var, long *int_var, float *float_var)
|
||||
{
|
||||
if (!var || !int_var || !float_var)
|
||||
if (!var || !int_var || !float_var || !var->Ptr)
|
||||
return 1; // îøèáêà: null óêàçàòåëü
|
||||
|
||||
char *addr = var->Ptr;
|
||||
unsigned long addr_val = (unsigned long)addr;
|
||||
|
||||
switch (var->ptr_type)
|
||||
{
|
||||
case pt_int8: // signed char
|
||||
*int_var = *((signed char *)var->Ptr);
|
||||
case pt_int8: // 8 áèò
|
||||
case pt_uint8:
|
||||
// âûðàâíèâàíèå íå íóæíî äëÿ 8 áèò
|
||||
*int_var = *((volatile char *)addr);
|
||||
break;
|
||||
|
||||
case pt_int16: // int
|
||||
*int_var = *((int *)var->Ptr);
|
||||
case pt_int16: // 16 áèò (int)
|
||||
case pt_uint16:
|
||||
if (addr_val & 0x1) // ïðîâåðêà âûðàâíèâàíèÿ ïî 2 áàéòàì
|
||||
return 2; // îøèáêà âûðàâíèâàíèÿ
|
||||
*int_var = *((volatile int *)addr);
|
||||
break;
|
||||
|
||||
case pt_int32: // long
|
||||
*int_var = *((long *)var->Ptr);
|
||||
case pt_int32: // 32 áèò (long)
|
||||
case pt_uint32:
|
||||
if (addr_val & 0x3) // ïðîâåðêà âûðàâíèâàíèÿ ïî 4 áàéòàì
|
||||
return 3; // îøèáêà âûðàâíèâàíèÿ
|
||||
*int_var = *((volatile long *)addr);
|
||||
break;
|
||||
|
||||
case pt_uint8: // unsigned char
|
||||
*int_var = *((unsigned char *)var->Ptr);
|
||||
break;
|
||||
|
||||
case pt_uint16: // unsigned int
|
||||
*int_var = *((unsigned int *)var->Ptr);
|
||||
break;
|
||||
|
||||
case pt_uint32: // unsigned long
|
||||
*int_var = *((unsigned long *)var->Ptr);
|
||||
break;
|
||||
|
||||
case pt_float: // float
|
||||
*float_var = *((float *)var->Ptr);
|
||||
// case pt_int64: // 64 áèò (long long)
|
||||
// case pt_uint64:
|
||||
// if (addr_val & 0x7) // ïðîâåðêà âûðàâíèâàíèÿ ïî 8 áàéòàì
|
||||
// return 2; // îøèáêà âûðàâíèâàíèÿ
|
||||
// // Òóò ïðîñòî ÷èòàåì, íî long long ìîæåò íå ïîìåñòèòüñÿ â *int_var
|
||||
// // Ìîæíî çàìåíèòü ëîãèêó ïîä 64-áèòíîå ÷òåíèå ïðè íåîáõîäèìîñòè
|
||||
// *int_var = *((volatile long long *)addr);
|
||||
// break;
|
||||
|
||||
case pt_float: // float (4 áàéòà)
|
||||
if (addr_val & 0x3) // ïðîâåðêà âûðàâíèâàíèÿ ïî 4 áàéòàì
|
||||
return 4; // îøèáêà âûðàâíèâàíèÿ
|
||||
*float_var = *((volatile float *)addr);
|
||||
break;
|
||||
|
||||
default:
|
||||
return 1; // íåïîääåðæèâàåìûé òèï
|
||||
// äëÿ óêàçàòåëåé è ìàññèâîâ íå ïîääåðæèâàåòñÿ ÷òåíèå
|
||||
// case pt_ptr_int8:
|
||||
// case pt_ptr_int16:
|
||||
@ -282,8 +398,6 @@ static int getDebugVar(DebugVar_t *var, long *int_var, float *float_var)
|
||||
// case pt_arr_uint8:
|
||||
// case pt_arr_uint16:
|
||||
// case pt_arr_uint32:
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0; // óñïåõ
|
||||
|
@ -9,9 +9,11 @@ typedef enum
|
||||
pt_int8, // signed char
|
||||
pt_int16, // int
|
||||
pt_int32, // long
|
||||
pt_int64, // long
|
||||
pt_uint8, // unsigned char
|
||||
pt_uint16, // unsigned int
|
||||
pt_uint32, // unsigned long
|
||||
pt_uint64, // unsigned long
|
||||
pt_float, // float
|
||||
pt_struct, // struct
|
||||
pt_union, // struct
|
||||
@ -70,12 +72,25 @@ typedef struct
|
||||
char* Ptr;
|
||||
DebugVarPtrType_t ptr_type;
|
||||
DebugVarIQType_t iq_type;
|
||||
char name[10];
|
||||
DebugVarIQType_t return_type;
|
||||
char name[11]; // 10 ñèìâîëîâ + '\0'
|
||||
}DebugVar_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint16_t year;
|
||||
uint8_t month;
|
||||
uint8_t day;
|
||||
uint8_t hour;
|
||||
uint8_t minute;
|
||||
} DateTimeHex;
|
||||
|
||||
|
||||
extern int DebugVar_Qnt;
|
||||
extern DebugVar_t dbg_vars[];
|
||||
long Debug_ReadVar(DebugVar_t *var, DebugVarIQType_t iq_type_final);
|
||||
|
||||
int Debug_LowLevel_ReadVar(DebugVar_t *var_ll, long *return_long);
|
||||
int Debug_ReadVar(DebugVar_t *var, long *return_long);
|
||||
int Debug_ReadVarName(DebugVar_t *var, char *name_ptr);
|
||||
|
||||
#endif //DEBUG_TOOLS
|
||||
|
55
debug_vars.c
55
debug_vars.c
@ -3,33 +3,33 @@
|
||||
|
||||
|
||||
// Èíêëþäû äëÿ äîñòóïà ê ïåðåìåííûì
|
||||
#include "vector.h"
|
||||
#include "f281xpwm.h"
|
||||
#include "log_can.h"
|
||||
#include "RS_Functions_modbus.h"
|
||||
#include "errors.h"
|
||||
#include "pwm_vector_regul.h"
|
||||
#include "xp_project.h"
|
||||
#include "xp_write_xpwm_time.h"
|
||||
#include "teta_calc.h"
|
||||
#include "vector.h"
|
||||
#include "v_pwm24.h"
|
||||
#include "errors.h"
|
||||
#include "dq_to_alphabeta_cos.h"
|
||||
#include "log_can.h"
|
||||
#include "f281xpwm.h"
|
||||
#include "pwm_vector_regul.h"
|
||||
#include "adc_tools.h"
|
||||
#include "rotation_speed.h"
|
||||
#include "dq_to_alphabeta_cos.h"
|
||||
#include "teta_calc.h"
|
||||
#include "CAN_Setup.h"
|
||||
#include "log_to_memory.h"
|
||||
#include "log_params.h"
|
||||
#include "CRC_Functions.h"
|
||||
#include "global_time.h"
|
||||
#include "RS_Functions.h"
|
||||
#include "detect_phase_break2.h"
|
||||
#include "x_parallel_bus.h"
|
||||
#include "x_serial_bus.h"
|
||||
#include "Spartan2E_Functions.h"
|
||||
#include "xp_controller.h"
|
||||
#include "xPeriphSP6_loader.h"
|
||||
#include "xp_rotation_sensor.h"
|
||||
#include "x_serial_bus.h"
|
||||
#include "Spartan2E_Functions.h"
|
||||
#include "x_parallel_bus.h"
|
||||
#include "svgen_dq.h"
|
||||
#include "detect_phase_break2.h"
|
||||
#include "log_to_memory.h"
|
||||
#include "CRC_Functions.h"
|
||||
#include "global_time.h"
|
||||
#include "CAN_Setup.h"
|
||||
#include "log_params.h"
|
||||
#include "pid_reg3.h"
|
||||
#include "IQmathLib.h"
|
||||
#include "doors_control.h"
|
||||
@ -314,9 +314,26 @@ extern int zero_ADC[20];
|
||||
|
||||
|
||||
// Îïðåäåëåíèå ìàññèâà ñ óêàçàòåëÿìè íà ïåðåìåííûå äëÿ îòëàäêè
|
||||
int DebugVar_Qnt = 2;
|
||||
int DebugVar_Qnt = 19;
|
||||
#pragma DATA_SECTION(dbg_vars,".dbgvar_info")
|
||||
DebugVar_t dbg_vars[] = {\
|
||||
{(char *)&ADC0finishAddr, pt_uint8, t_iq7, pt_uint8, "asdasjjjjj" }, \
|
||||
{(char *)&project.cds_tk.count_elements_pbus, pt_uint16, t_iq_none, pt_uint16, "project.cd" }, \
|
||||
{(char *)&ADC1startAddr, pt_int16, t_iq_none, pt_int16, "ADC1StrAdr" }, \
|
||||
{(char *)&project.cds_tk[0].plane_address, pt_uint16, t_iq_none, pt_uint16, "tk0_Adr" }, \
|
||||
{(char *)&ADC_sf[0][0], pt_int16, t_iq_none, pt_int16, "ADC_sf00" }, \
|
||||
{(char *)&ADC_sf[0][1], pt_int16, t_iq_none, pt_int16, "ADC_sf01" }, \
|
||||
{(char *)&ADC_sf[0][2], pt_int16, t_iq_none, pt_int16, "ADC_sf02" }, \
|
||||
{(char *)&ADC_sf[0][3], pt_int16, t_iq_none, pt_int16, "ADC_sf03" }, \
|
||||
{(char *)&ADC_sf[0][4], pt_int16, t_iq_none, pt_int16, "ADC_sf04" }, \
|
||||
{(char *)&ADC_sf[0][5], pt_int16, t_iq_none, pt_int16, "ADC_sf05" }, \
|
||||
{(char *)&ADC_sf[0][6], pt_int16, t_iq_none, pt_int16, "ADC_sf06" }, \
|
||||
{(char *)&ADC_sf[0][7], pt_int16, t_iq_none, pt_int16, "ADC_sf07" }, \
|
||||
{(char *)&ADC_sf[0][8], pt_int16, t_iq_none, pt_int16, "ADC_sf08" }, \
|
||||
{(char *)&ADC_sf[0][9], pt_int16, t_iq_none, pt_int16, "ADC_sf09" }, \
|
||||
{(char *)&ADC_sf[0][10], pt_int16, t_iq_none, pt_int16, "ADC_sf010" }, \
|
||||
{(char *)&ADC_sf[0][11], pt_int16, t_iq_none, pt_int16, "ADC_sf011" }, \
|
||||
{(char *)&ADC_sf[0][12], pt_int16, t_iq_none, pt_int16, "ADC_sf012" }, \
|
||||
{(char *)&ADC_sf[0][13], pt_int16, t_iq_none, pt_int16, "ADC_sf013" }, \
|
||||
{(char *)&ADC_sf[0][14], pt_int16, t_iq_none, pt_int16, "ADC_sf014" }, \
|
||||
{(char *)&ADC_sf[0][15], pt_int16, t_iq_none, pt_int16, "ADC_sf015" }, \
|
||||
{(char *)&project.cds_tk[0].read.sbus.mask_protect_tk.all, pt_uint16, t_iq_none, pt_uint16, "project.cd" }, \
|
||||
};
|
||||
|
2860
structs.xml
2860
structs.xml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user