Compare commits
14 Commits
Author | SHA1 | Date | |
---|---|---|---|
e99de603e6 | |||
788ad19464 | |||
96496a0256 | |||
f89aff1b1c | |||
6830743477 | |||
171f176d63 | |||
f2c4b7b3cd | |||
c94a7e711c | |||
5be6343c33 | |||
043359fe66 | |||
c55f38ef1c | |||
ae2c90160e | |||
4de53090a1 | |||
742c4e9e1b |
3
.gitignore
vendored
3
.gitignore
vendored
@ -4,4 +4,5 @@
|
|||||||
/build_temp
|
/build_temp
|
||||||
/DebugVarEdit_GUI.build
|
/DebugVarEdit_GUI.build
|
||||||
/DebugVarEdit_GUI.dist
|
/DebugVarEdit_GUI.dist
|
||||||
/DebugVarEdit_GUI.onefile-build
|
/DebugVarEdit_GUI.onefile-build
|
||||||
|
/parse_xml/build/
|
||||||
|
BIN
DebugTools.rar
Normal file
BIN
DebugTools.rar
Normal file
Binary file not shown.
BIN
DebugVarEdit.exe
BIN
DebugVarEdit.exe
Binary file not shown.
50
README.md
50
README.md
@ -1,23 +1,56 @@
|
|||||||
# DebugVarEdit — Утилита для генерации переменных для отладки
|
# DebugTools - Просмотр переменных по указателям
|
||||||
|
Модуль состоит из трех файлов:
|
||||||
|
- **debug_tools.c** - реализация считывания переменных
|
||||||
|
- **debug_tools.h** - объявление всякого для считывания переменных
|
||||||
|
- **debug_vars.c** - определение массива считываемых переменных
|
||||||
|
|
||||||
|
Этот модуль предоставляет функциональность для чтения значений переменных во встроенной системе, включая работу с IQ-форматами, защиту доступа и проверку диапазонов памяти.
|
||||||
|
|
||||||
|
Для чтения переменных можно использовать функции:
|
||||||
|
|
||||||
|
```c
|
||||||
|
int Debug_ReadVar(int var_ind, int32_t *return_long);
|
||||||
|
int Debug_ReadVarName(int var_ind, DebugVarName_t name_ptr);
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Переменные доступные для чтения определяются в **debug_vars.c** (их можно прописывать вручную или генерировать через **DebugVarEdit**):
|
||||||
|
|
||||||
|
```c
|
||||||
|
// Определение массива с указателями на переменные для отладки
|
||||||
|
int DebugVar_Qnt = 5;
|
||||||
|
#pragma DATA_SECTION(dbg_vars,".dbgvar_info")
|
||||||
|
// pointer_type iq_type return_iq_type short_name
|
||||||
|
DebugVar_t dbg_vars[] = {\
|
||||||
|
{(uint8_t *)&freqTerm, pt_float, t_iq_none, t_iq10, "freqT" }, \
|
||||||
|
{(uint8_t *)&ADC_sf[0][0], pt_int16, t_iq_none, t_iq_none, "ADC_sf00" }, \
|
||||||
|
{(uint8_t *)&ADC_sf[0][1], pt_int16, t_iq_none, t_iq_none, "ADC_sf01" }, \
|
||||||
|
{(uint8_t *)&ADC_sf[0][2], pt_int16, t_iq_none, t_iq_none, "ADC_sf02" }, \
|
||||||
|
{(uint8_t *)&ADC_sf[0][3], pt_int16, t_iq_none, t_iq_none, "ADC_sf03" }, \
|
||||||
|
{(uint8_t *)&Bender[0].KOhms, pt_uint16, t_iq, t_iq10, "Bend0.KOhm" }, \
|
||||||
|
{(uint8_t *)&Bender[0].Times, pt_uint16, t_iq_none, t_iq_none, "Bend0.Time" }, \
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
# DebugVarEdit - Настройка переменных
|
||||||
**DebugVarEdit** — графическое приложение для Windows, предназначенное для настройки и генерации отладочных переменных (`debug_vars.c`) на основе исходного C-проекта. Работает с `makefile` проекта, сохраняет изменения в XML и позволяет удобно редактировать переменные и их типы через интерфейс.
|
**DebugVarEdit** — графическое приложение для Windows, предназначенное для настройки и генерации отладочных переменных (`debug_vars.c`) на основе исходного C-проекта. Работает с `makefile` проекта, сохраняет изменения в XML и позволяет удобно редактировать переменные и их типы через интерфейс.
|
||||||
|
|
||||||
Программа — один исполняемый файл `DebugVarEdit.exe`, не требующий установки и дополнительных зависимостей.
|
Программа — один исполняемый файл `DebugVarEdit.exe`, не требующий установки и дополнительных зависимостей.
|
||||||
|
|
||||||
> Требуется Windows 7 или новее.
|
> Требуется Windows 7 или новее.
|
||||||
|
|
||||||
> [Для разработчиков](#для-разработчиков)
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Как использовать
|
## Как использовать приложение
|
||||||
|
|
||||||
1. Запустите **DebugVarEdit.exe.**
|
1. Запустите **DebugVarEdit.exe.**
|
||||||
|
|
||||||
2. Укажите пути:
|
2. Укажите пути и контроллер:
|
||||||
- **Путь к XML** — новый или существующий файл настроек переменных;
|
- **Путь к XML** — новый или существующий файл настроек переменных;
|
||||||
- **Путь к проекту** — путь к корню проека (папка, которая является корнем для проекта в Code Composer);
|
- **Путь к проекту** — путь к корню проека (папка, которая является корнем для проекта в Code Composer);
|
||||||
- **Путь к makefile** — путь к `makefile` относительно корня проекта;
|
- **Путь к makefile** — путь к `makefile` относительно корня проекта;
|
||||||
- **Путь для debug_vars.c** — папка, куда будет сгенерирован файл `debug_vars.c` с переменными.
|
- **Путь для debug_vars.c** — папка, куда будет сгенерирован файл `debug_vars.c` с переменными.
|
||||||
|
- **МК** — TMS или STM. Они отличаются размером int, и также принятыми кодировками файлов (utf-8/cp1251)
|
||||||
|
|
||||||
3. Нажмите **Сканировать переменные**:
|
3. Нажмите **Сканировать переменные**:
|
||||||
- Программа проанализирует исходники, указанные в makefile, и найдёт все переменные.
|
- Программа проанализирует исходники, указанные в makefile, и найдёт все переменные.
|
||||||
@ -61,7 +94,7 @@
|
|||||||
## Пример XML-файла
|
## Пример XML-файла
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<project proj_path="C:/myproj" makefile_path="Debug/Makefile" structs_path="debugVars/structs.xml">
|
<project proj_path="C:/myproj" makefile_path="Debug/Makefile" structs_path="Src/DebugTools/structs.xml">
|
||||||
<variables>
|
<variables>
|
||||||
<var name="g_myvar">
|
<var name="g_myvar">
|
||||||
<enable>true</enable>
|
<enable>true</enable>
|
||||||
@ -79,6 +112,7 @@
|
|||||||
</project>
|
</project>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
---
|
---
|
||||||
|
|
||||||
# Для разработчиков
|
# Для разработчиков
|
||||||
@ -107,14 +141,14 @@ Src
|
|||||||
|
|
||||||
Для запуска приложения:
|
Для запуска приложения:
|
||||||
- **Python 3.7+**
|
- **Python 3.7+**
|
||||||
- **clang** — используется для парсинга C-кода (требуется `libclang.dll`)
|
- **clang** — используется для парсинга C-кода (требуется `libclang.dll`, лежит в папке Src)
|
||||||
- **PySide2** — GUI-фреймворк
|
- **PySide2** — GUI-фреймворк
|
||||||
- **lxml** — работа с XML
|
- **lxml** — работа с XML
|
||||||
> Python 3.7 и PySide2 рекомендуется для совместимости с Windows 7
|
> Python 3.7 и PySide2 рекомендуется для совместимости с Windows 7
|
||||||
|
|
||||||
Для сборки `.exe`:
|
Для сборки `.exe`:
|
||||||
- **Nuitka** — создает полностью автономный `.exe` без внешних зависимостей
|
- **Nuitka** — создает полностью автономный `.exe` без внешних зависимостей
|
||||||
- **PyInstaller** — создает `.exe` с зависимостью от `pythonXX.dll` (Python должен быть установлен)
|
- **PyInstaller** — создает `.exe` с зависимостью от `pythonXX.dll` (Python должен быть установлен для запуска `.exe`)
|
||||||
|
|
||||||
|
|
||||||
### Сборка:
|
### Сборка:
|
||||||
@ -134,5 +168,5 @@ Src
|
|||||||
### Установка зависимостей
|
### Установка зависимостей
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install PySide2 lxml nuitka pyinstaller
|
pip install clang PySide2 lxml nuitka pyinstaller
|
||||||
```
|
```
|
@ -5,8 +5,9 @@ import sys
|
|||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import lxml.etree as ET
|
import lxml.etree as ET
|
||||||
from generate_debug_vars import type_map
|
from generate_debug_vars import type_map, choose_type_map
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
|
from tms_debugvar_term import _DemoWindow
|
||||||
import threading
|
import threading
|
||||||
from generate_debug_vars import run_generate
|
from generate_debug_vars import run_generate
|
||||||
import var_setup
|
import var_setup
|
||||||
@ -17,12 +18,14 @@ import scan_vars
|
|||||||
import myXML
|
import myXML
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
|
||||||
from PySide2.QtWidgets import (
|
from PySide2.QtWidgets import (
|
||||||
QApplication, QWidget, QTableWidget, QTableWidgetItem,
|
QApplication, QWidget, QTableWidget, QTableWidgetItem,
|
||||||
QCheckBox, QComboBox, QLineEdit, QVBoxLayout, QHBoxLayout, QPushButton,
|
QCheckBox, QComboBox, QLineEdit, QVBoxLayout, QHBoxLayout, QPushButton,
|
||||||
QCompleter, QAbstractItemView, QLabel, QMessageBox, QFileDialog, QTextEdit,
|
QCompleter, QAbstractItemView, QLabel, QMessageBox, QFileDialog, QTextEdit,
|
||||||
QDialog, QTreeWidget, QTreeWidgetItem, QSizePolicy, QHeaderView
|
QDialog, QTreeWidget, QTreeWidgetItem, QSizePolicy, QHeaderView,
|
||||||
)
|
QMenuBar, QMenu, QAction
|
||||||
|
)
|
||||||
from PySide2.QtGui import QTextCursor, QKeyEvent, QIcon, QFont
|
from PySide2.QtGui import QTextCursor, QKeyEvent, QIcon, QFont
|
||||||
from PySide2.QtCore import Qt, QProcess, QObject, Signal, QSettings
|
from PySide2.QtCore import Qt, QProcess, QObject, Signal, QSettings
|
||||||
|
|
||||||
@ -58,6 +61,7 @@ class VarEditor(QWidget):
|
|||||||
self.output_path = None
|
self.output_path = None
|
||||||
self._updating = False # Флаг блокировки рекурсии
|
self._updating = False # Флаг блокировки рекурсии
|
||||||
self._resizing = False # флаг блокировки повторного вызова
|
self._resizing = False # флаг блокировки повторного вызова
|
||||||
|
self.target = 'TMS'
|
||||||
self.initUI()
|
self.initUI()
|
||||||
|
|
||||||
def initUI(self):
|
def initUI(self):
|
||||||
@ -122,6 +126,43 @@ class VarEditor(QWidget):
|
|||||||
self.btn_update_vars = QPushButton(scan_title)
|
self.btn_update_vars = QPushButton(scan_title)
|
||||||
self.btn_update_vars.clicked.connect(self.update_vars_data)
|
self.btn_update_vars.clicked.connect(self.update_vars_data)
|
||||||
|
|
||||||
|
# Добавляем чекбокс для выбора типовой карты
|
||||||
|
# --- Создаем верхнее меню ---
|
||||||
|
menubar = QMenuBar(self)
|
||||||
|
menubar.setToolTip('Разные размеры int и кодировки файлов')
|
||||||
|
self.target_menu = QMenu("МК:", menubar)
|
||||||
|
# Создаем действия для выбора Target
|
||||||
|
self.action_tms = QAction("TMS", self, checkable=True)
|
||||||
|
self.action_stm = QAction("STM", self, checkable=True)
|
||||||
|
# Инициализируем QSettings с именем организации и приложения
|
||||||
|
self.settings = QSettings("SET", "DebugVarEdit_MainWindow")
|
||||||
|
# Восстанавливаем сохранённое состояние, если есть
|
||||||
|
mcu = self.settings.value("mcu_choosen", True, type=str)
|
||||||
|
self.on_target_selected(mcu)
|
||||||
|
|
||||||
|
self.target_menu.setToolTip(f'TMS: Размер int 16 бит. Кодировка cp1251\nSTM: Размер int 32 бита. Кодировка utf-8')
|
||||||
|
# Группируем действия чтобы выбирался только один
|
||||||
|
self.action_tms.triggered.connect(lambda: self.on_target_selected("TMS"))
|
||||||
|
self.action_tms.setToolTip('Размер int 16 бит. Кодировка cp1251')
|
||||||
|
self.action_stm.triggered.connect(lambda: self.on_target_selected("STM"))
|
||||||
|
self.action_stm.setToolTip('Размер int 32 бита. Кодировка utf-8')
|
||||||
|
|
||||||
|
self.target_menu.addAction(self.action_tms)
|
||||||
|
self.target_menu.addAction(self.action_stm)
|
||||||
|
|
||||||
|
self.terminal_menu = QMenu("Открыть Терминал", menubar)
|
||||||
|
|
||||||
|
self.action_terminal_tms = QAction("TMS DemoTerminal", self)
|
||||||
|
self.action_terminal_modbus = QAction("Modbus DemoTerminal", self)
|
||||||
|
self.action_terminal_tms.triggered.connect(lambda: self.open_terminal("TMS"))
|
||||||
|
self.action_terminal_modbus.triggered.connect(lambda: self.open_terminal("MODBUS"))
|
||||||
|
|
||||||
|
self.terminal_menu.addAction(self.action_terminal_tms)
|
||||||
|
#self.terminal_menu.addAction(self.action_terminal_modbus)
|
||||||
|
|
||||||
|
menubar.addMenu(self.target_menu)
|
||||||
|
menubar.addMenu(self.terminal_menu)
|
||||||
|
|
||||||
# Кнопка сохранения
|
# Кнопка сохранения
|
||||||
btn_save = QPushButton(build_title)
|
btn_save = QPushButton(build_title)
|
||||||
btn_save.clicked.connect(self.save_build)
|
btn_save.clicked.connect(self.save_build)
|
||||||
@ -137,6 +178,7 @@ class VarEditor(QWidget):
|
|||||||
self.table = VariableTableWidget()
|
self.table = VariableTableWidget()
|
||||||
# Основной layout
|
# Основной layout
|
||||||
layout = QVBoxLayout()
|
layout = QVBoxLayout()
|
||||||
|
layout.setMenuBar(menubar) # прикрепляем menubar в layout сверху
|
||||||
layout.addLayout(xml_layout)
|
layout.addLayout(xml_layout)
|
||||||
layout.addLayout(proj_layout)
|
layout.addLayout(proj_layout)
|
||||||
layout.addLayout(makefile_layout)
|
layout.addLayout(makefile_layout)
|
||||||
@ -149,8 +191,30 @@ class VarEditor(QWidget):
|
|||||||
|
|
||||||
self.setLayout(layout)
|
self.setLayout(layout)
|
||||||
|
|
||||||
|
def open_terminal(self, target):
|
||||||
|
target = target.lower()
|
||||||
|
|
||||||
|
if target == "tms":
|
||||||
|
self.terminal_widget = _DemoWindow() # _DemoWindow наследует QWidget
|
||||||
|
self.terminal_widget.show()
|
||||||
|
elif target == "modbus":
|
||||||
|
a=1
|
||||||
|
|
||||||
|
|
||||||
|
def on_target_selected(self, target):
|
||||||
|
self.target_menu.setTitle(f'МК: {target}')
|
||||||
|
self.settings.setValue("mcu_choosen", target)
|
||||||
|
self.target = target.lower()
|
||||||
|
if self.target == "stm":
|
||||||
|
choose_type_map(True)
|
||||||
|
self.action_stm.setChecked(True)
|
||||||
|
self.action_tms.setChecked(False)
|
||||||
|
else:
|
||||||
|
choose_type_map(False)
|
||||||
|
self.action_tms.setChecked(True)
|
||||||
|
self.action_stm.setChecked(False)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_xml_path(self):
|
def get_xml_path(self):
|
||||||
xml_path = self.xml_output_edit.text().strip()
|
xml_path = self.xml_output_edit.text().strip()
|
||||||
return xml_path
|
return xml_path
|
||||||
@ -241,7 +305,7 @@ class VarEditor(QWidget):
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
run_generate(self.proj_path, self.xml_path, self.output_path)
|
run_generate(self.proj_path, self.xml_path, self.output_path, self.table._shortname_size)
|
||||||
QMessageBox.information(self, "Готово", "Файл debug_vars.c успешно сгенерирован.")
|
QMessageBox.information(self, "Готово", "Файл debug_vars.c успешно сгенерирован.")
|
||||||
self.update()
|
self.update()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -358,15 +422,25 @@ class VarEditor(QWidget):
|
|||||||
super().keyPressEvent(event)
|
super().keyPressEvent(event)
|
||||||
|
|
||||||
def __browse_makefile(self):
|
def __browse_makefile(self):
|
||||||
|
if self.target == 'stm':
|
||||||
|
file_filter = "Makefile или Keil-проект (*.uvprojx *.uvproj makefile);;Все файлы (*)"
|
||||||
|
dialog_title = "Выберите Makefile или Keil-проект"
|
||||||
|
else: # 'TMS' или по умолчанию
|
||||||
|
file_filter = "Makefile (makefile);;Все файлы (*)"
|
||||||
|
dialog_title = "Выберите Makefile"
|
||||||
|
|
||||||
file_path, _ = QFileDialog.getOpenFileName(
|
file_path, _ = QFileDialog.getOpenFileName(
|
||||||
self, "Выберите Makefile", filter="Makefile (makefile);;All Files (*)"
|
self,
|
||||||
|
dialog_title,
|
||||||
|
filter=file_filter
|
||||||
)
|
)
|
||||||
if file_path and self.proj_path:
|
if file_path:
|
||||||
path = myXML.make_relative_path(file_path, self.proj_path)
|
if self.proj_path:
|
||||||
else:
|
path = myXML.make_relative_path(file_path, self.proj_path)
|
||||||
path = file_path
|
else:
|
||||||
self.makefile_edit.setText(path)
|
path = file_path
|
||||||
self.makefile_path = path
|
self.makefile_edit.setText(path)
|
||||||
|
self.makefile_path = path
|
||||||
|
|
||||||
def __browse_source_output(self):
|
def __browse_source_output(self):
|
||||||
dir_path = QFileDialog.getExistingDirectory(self, "Выберите папку для debug_vars.c")
|
dir_path = QFileDialog.getExistingDirectory(self, "Выберите папку для debug_vars.c")
|
||||||
@ -438,12 +512,12 @@ class VarEditor(QWidget):
|
|||||||
self.write_to_xml()
|
self.write_to_xml()
|
||||||
|
|
||||||
|
|
||||||
def __open_variable_selector(self):
|
def __open_variable_selector(self):
|
||||||
|
self.update()
|
||||||
if not self.vars_list:
|
if not self.vars_list:
|
||||||
QMessageBox.warning(self, "Нет переменных", f"Сначала загрузите переменные ({scan_title}).")
|
QMessageBox.warning(self, "Нет переменных", f"Сначала загрузите переменные ({scan_title}).")
|
||||||
return
|
return
|
||||||
|
|
||||||
self.update()
|
|
||||||
dlg = VariableSelectorDialog(self.table, 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_():
|
if dlg.exec_():
|
||||||
self.write_to_xml()
|
self.write_to_xml()
|
||||||
|
438
Src/allvars_xml_parser.py
Normal file
438
Src/allvars_xml_parser.py
Normal file
@ -0,0 +1,438 @@
|
|||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
import var_setup
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import List, Dict, Optional, Tuple
|
||||||
|
from PySide2.QtWidgets import (
|
||||||
|
QDialog, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QPushButton,
|
||||||
|
QLineEdit, QLabel, QHeaderView, QCompleter, QCheckBox, QHBoxLayout, QSizePolicy,
|
||||||
|
QTableWidget, QTableWidgetItem, QFileDialog, QWidget, QMessageBox, QApplication, QMainWindow
|
||||||
|
)
|
||||||
|
from PySide2 import QtCore, QtGui
|
||||||
|
from path_hints import PathHints
|
||||||
|
from generate_debug_vars import choose_type_map, type_map
|
||||||
|
from var_selector_window import VariableSelectorDialog
|
||||||
|
from typing import List, Tuple, Optional, Dict, Any, Set
|
||||||
|
|
||||||
|
DATE_FIELD_SET = {'year','month','day','hour','minute'}
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MemberNode:
|
||||||
|
name: str
|
||||||
|
offset: int = 0
|
||||||
|
type_str: str = ''
|
||||||
|
size: Optional[int] = None
|
||||||
|
children: List['MemberNode'] = field(default_factory=list)
|
||||||
|
# --- новые, но необязательные (совместимость) ---
|
||||||
|
kind: Optional[str] = None # 'array', 'union', ...
|
||||||
|
count: Optional[int] = None # size1 (число элементов в массиве)
|
||||||
|
|
||||||
|
def is_date_struct(self) -> bool:
|
||||||
|
if not self.children:
|
||||||
|
return False
|
||||||
|
child_names = {c.name for c in self.children}
|
||||||
|
return DATE_FIELD_SET.issubset(child_names)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class VariableNode:
|
||||||
|
name: str
|
||||||
|
address: int
|
||||||
|
type_str: str
|
||||||
|
size: Optional[int]
|
||||||
|
members: List[MemberNode] = field(default_factory=list)
|
||||||
|
# --- новые, но необязательные ---
|
||||||
|
kind: Optional[str] = None # 'array'
|
||||||
|
count: Optional[int] = None # size1
|
||||||
|
|
||||||
|
def base_address_hex(self) -> str:
|
||||||
|
return f"0x{self.address:06X}"
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------- XML Parser ----------------------------
|
||||||
|
|
||||||
|
class VariablesXML:
|
||||||
|
"""
|
||||||
|
Reads your XML and outputs a flat list of paths:
|
||||||
|
- Arrays -> name[i], multilevel -> name[i][j]
|
||||||
|
- Pointer to struct -> children via '->'
|
||||||
|
- Regular struct -> children via '.'
|
||||||
|
"""
|
||||||
|
# assumed primitive sizes (for STM/MCU: int=2)
|
||||||
|
_PRIM_SIZE = {
|
||||||
|
'char':1, 'signed char':1, 'unsigned char':1, 'uint8_t':1, 'int8_t':1,
|
||||||
|
'short':2, 'short int':2, 'signed short':2, 'unsigned short':2,
|
||||||
|
'uint16_t':2, 'int16_t':2,
|
||||||
|
'int':2, 'signed int':2, 'unsigned int':2,
|
||||||
|
'long':4, 'unsigned long':4, 'int32_t':4, 'uint32_t':4,
|
||||||
|
'float':4,
|
||||||
|
'long long':8, 'unsigned long long':8, 'int64_t':8, 'uint64_t':8, 'double':8,
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, path: str):
|
||||||
|
self.path = path
|
||||||
|
self.timestamp: str = ''
|
||||||
|
self.variables: List[VariableNode] = []
|
||||||
|
choose_type_map(0)
|
||||||
|
self._parse()
|
||||||
|
|
||||||
|
# ------------------ low helpers ------------------
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _parse_int_guess(txt: Optional[str]) -> Optional[int]:
|
||||||
|
if not txt:
|
||||||
|
return None
|
||||||
|
txt = txt.strip()
|
||||||
|
if txt.startswith(('0x','0X')):
|
||||||
|
return int(txt, 16)
|
||||||
|
# если в строке есть буквы A-F → возможно hex
|
||||||
|
if any(c in 'abcdefABCDEF' for c in txt):
|
||||||
|
try:
|
||||||
|
return int(txt, 16)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
return int(txt, 10)
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _is_pointer_to_struct(t: str) -> bool:
|
||||||
|
if not t:
|
||||||
|
return False
|
||||||
|
low = t.replace('\t',' ').replace('\n',' ')
|
||||||
|
return 'struct ' in low and '*' in low
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _is_struct_or_union(t: str) -> bool:
|
||||||
|
if not t:
|
||||||
|
return False
|
||||||
|
low = t.strip()
|
||||||
|
return low.startswith('struct ') or low.startswith('union ')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _strip_array_suffix(t: str) -> str:
|
||||||
|
return t[:-2].strip() if t.endswith('[]') else t
|
||||||
|
|
||||||
|
def _guess_primitive_size(self, type_str: str) -> Optional[int]:
|
||||||
|
if not type_str:
|
||||||
|
return None
|
||||||
|
base = type_str
|
||||||
|
for tok in ('volatile','const'):
|
||||||
|
base = base.replace(tok, '')
|
||||||
|
base = base.replace('*',' ')
|
||||||
|
base = base.replace('[',' ').replace(']',' ')
|
||||||
|
base = ' '.join(base.split()).strip()
|
||||||
|
return self._PRIM_SIZE.get(base)
|
||||||
|
|
||||||
|
# ------------------ XML read ------------------
|
||||||
|
|
||||||
|
def _parse(self):
|
||||||
|
try:
|
||||||
|
tree = ET.parse(self.path)
|
||||||
|
root = tree.getroot()
|
||||||
|
|
||||||
|
ts = root.find('timestamp')
|
||||||
|
self.timestamp = ts.text.strip() if ts is not None and ts.text else ''
|
||||||
|
|
||||||
|
def parse_member(elem) -> MemberNode:
|
||||||
|
name = elem.get('name','')
|
||||||
|
offset = int(elem.get('offset','0'),16) if elem.get('offset') else 0
|
||||||
|
t = elem.get('type','') or ''
|
||||||
|
size_attr = elem.get('size')
|
||||||
|
size = int(size_attr,16) if size_attr else None
|
||||||
|
kind = elem.get('kind')
|
||||||
|
size1_attr = elem.get('size1')
|
||||||
|
count = None
|
||||||
|
if size1_attr:
|
||||||
|
count = self._parse_int_guess(size1_attr)
|
||||||
|
node = MemberNode(name=name, offset=offset, type_str=t, size=size,
|
||||||
|
kind=kind, count=count)
|
||||||
|
for ch in elem.findall('member'):
|
||||||
|
node.children.append(parse_member(ch))
|
||||||
|
return node
|
||||||
|
|
||||||
|
for var in root.findall('variable'):
|
||||||
|
addr = int(var.get('address','0'),16)
|
||||||
|
name = var.get('name','')
|
||||||
|
t = var.get('type','') or ''
|
||||||
|
size_attr = var.get('size')
|
||||||
|
size = int(size_attr,16) if size_attr else None
|
||||||
|
kind = var.get('kind')
|
||||||
|
size1_attr = var.get('size1')
|
||||||
|
count = None
|
||||||
|
if size1_attr:
|
||||||
|
count = self._parse_int_guess(size1_attr)
|
||||||
|
members = [parse_member(m) for m in var.findall('member')]
|
||||||
|
self.variables.append(
|
||||||
|
VariableNode(name=name, address=addr, type_str=t, size=size,
|
||||||
|
members=members, kind=kind, count=count)
|
||||||
|
)
|
||||||
|
except FileNotFoundError:
|
||||||
|
self.variables = []
|
||||||
|
except ET.ParseError:
|
||||||
|
self.variables = []
|
||||||
|
|
||||||
|
# ------------------ flatten (expanded) ------------------
|
||||||
|
|
||||||
|
def flattened(self,
|
||||||
|
max_array_elems: Optional[int] = None
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
Returns a list of dictionaries with full data for variables and their expanded members.
|
||||||
|
Each dictionary contains: 'name', 'address', 'type', 'size', 'kind', 'count'.
|
||||||
|
max_array_elems: limit unfolding of large arrays (None = all).
|
||||||
|
"""
|
||||||
|
out: List[Dict[str, Any]] = []
|
||||||
|
|
||||||
|
def get_dict(name: str, address: int, type_str: str, size: Optional[int], kind: Optional[str], count: Optional[int]) -> Dict[str, Any]:
|
||||||
|
"""Helper to create the output dictionary format."""
|
||||||
|
return {
|
||||||
|
'name': name,
|
||||||
|
'address': address,
|
||||||
|
'type': type_str,
|
||||||
|
'size': size,
|
||||||
|
'kind': kind,
|
||||||
|
'count': count
|
||||||
|
}
|
||||||
|
|
||||||
|
def compute_stride(size_bytes: Optional[int],
|
||||||
|
count: Optional[int],
|
||||||
|
base_type: Optional[str],
|
||||||
|
node_children: Optional[List[MemberNode]]) -> int:
|
||||||
|
"""Calculates the stride (size of one element) for arrays."""
|
||||||
|
# 1) size_bytes/count
|
||||||
|
if size_bytes and count and count > 0:
|
||||||
|
if size_bytes % count == 0:
|
||||||
|
stride = size_bytes // count
|
||||||
|
if stride <= 0:
|
||||||
|
stride = 1
|
||||||
|
return stride
|
||||||
|
else:
|
||||||
|
# size not divisible by count → most likely size = size of one element
|
||||||
|
return max(size_bytes, 1)
|
||||||
|
|
||||||
|
# 2) attempt by type (primitive)
|
||||||
|
if base_type:
|
||||||
|
gs = self._guess_primitive_size(base_type)
|
||||||
|
if gs:
|
||||||
|
return gs
|
||||||
|
|
||||||
|
# 3) attempt by children (structure)
|
||||||
|
if node_children:
|
||||||
|
if not node_children:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
min_off = min(ch.offset for ch in node_children)
|
||||||
|
max_end = min_off
|
||||||
|
for ch in node_children:
|
||||||
|
sz = ch.size
|
||||||
|
if not sz:
|
||||||
|
sz = self._guess_primitive_size(ch.type_str) or 1
|
||||||
|
end = ch.offset + sz
|
||||||
|
if end > max_end:
|
||||||
|
max_end = end
|
||||||
|
stride = max_end - min_off
|
||||||
|
if stride > 0:
|
||||||
|
return stride
|
||||||
|
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
def expand_members(prefix_name: str,
|
||||||
|
base_addr: int,
|
||||||
|
members: List[MemberNode],
|
||||||
|
parent_is_ptr_struct: bool):
|
||||||
|
"""
|
||||||
|
Recursively expands members of structs/unions or pointed-to structs.
|
||||||
|
parent_is_ptr_struct: if True, connection is '->' otherwise '.'
|
||||||
|
"""
|
||||||
|
join = '->' if parent_is_ptr_struct else '.'
|
||||||
|
for m in members:
|
||||||
|
path_m = f"{prefix_name}{join}{m.name}" if prefix_name else m.name
|
||||||
|
addr_m = base_addr + m.offset
|
||||||
|
out.append(get_dict(path_m, addr_m, m.type_str, m.size, m.kind, m.count))
|
||||||
|
|
||||||
|
# array?
|
||||||
|
if (m.kind == 'array') or m.type_str.endswith('[]'):
|
||||||
|
count = m.count
|
||||||
|
if count is None:
|
||||||
|
count = 0
|
||||||
|
if count <= 0:
|
||||||
|
continue
|
||||||
|
base_t = self._strip_array_suffix(m.type_str)
|
||||||
|
stride = compute_stride(m.size, count, base_t, m.children if m.children else None)
|
||||||
|
limit = count if max_array_elems is None else min(count, max_array_elems)
|
||||||
|
for i in range(limit):
|
||||||
|
path_i = f"{path_m}[{i}]"
|
||||||
|
addr_i = addr_m + i*stride
|
||||||
|
# Determine kind for array element based on its base type
|
||||||
|
elem_kind = None
|
||||||
|
if self._is_struct_or_union(base_t):
|
||||||
|
elem_kind = 'struct' # or 'union' depending on `base_t` prefix
|
||||||
|
elif self._guess_primitive_size(base_t):
|
||||||
|
elem_kind = 'primitive'
|
||||||
|
|
||||||
|
# For array elements, 'size' is the stride (size of one element), 'count' is None.
|
||||||
|
out.append(get_dict(path_i, addr_i, base_t, stride, elem_kind, None))
|
||||||
|
|
||||||
|
# array element: if structure / union → unfold fields
|
||||||
|
if m.children and self._is_struct_or_union(base_t):
|
||||||
|
expand_members(path_i, addr_i, m.children, parent_is_ptr_struct=False)
|
||||||
|
# array element: if pointer to structure
|
||||||
|
elif self._is_pointer_to_struct(base_t):
|
||||||
|
# usually no children in XML for these, but if present — use them
|
||||||
|
expand_members(path_i, addr_i, m.children, parent_is_ptr_struct=True)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# not an array, but has children (e.g., struct/union)
|
||||||
|
if m.children:
|
||||||
|
is_ptr_struct = self._is_pointer_to_struct(m.type_str)
|
||||||
|
expand_members(path_m, addr_m, m.children, parent_is_ptr_struct=is_ptr_struct)
|
||||||
|
|
||||||
|
# --- top-level variables ---
|
||||||
|
for v in self.variables:
|
||||||
|
out.append(get_dict(v.name, v.address, v.type_str, v.size, v.kind, v.count))
|
||||||
|
|
||||||
|
# top-level array?
|
||||||
|
if (v.kind == 'array') or v.type_str.endswith('[]'):
|
||||||
|
count = v.count
|
||||||
|
if count is None:
|
||||||
|
count = 0
|
||||||
|
if count > 0:
|
||||||
|
base_t = self._strip_array_suffix(v.type_str)
|
||||||
|
stride = compute_stride(v.size, count, base_t, v.members if v.members else None)
|
||||||
|
limit = count if max_array_elems is None else min(count, max_array_elems)
|
||||||
|
for i in range(limit):
|
||||||
|
p = f"{v.name}[{i}]"
|
||||||
|
a = v.address + i*stride
|
||||||
|
# Determine kind for array element
|
||||||
|
elem_kind = None
|
||||||
|
if self._is_struct_or_union(base_t):
|
||||||
|
elem_kind = 'struct' # or 'union'
|
||||||
|
elif self._guess_primitive_size(base_t):
|
||||||
|
elem_kind = 'primitive'
|
||||||
|
|
||||||
|
out.append(get_dict(p, a, base_t, stride, elem_kind, None))
|
||||||
|
|
||||||
|
# array of structs?
|
||||||
|
if v.members and self._is_struct_or_union(base_t):
|
||||||
|
expand_members(p, a, v.members, parent_is_ptr_struct=False)
|
||||||
|
# array of pointers to structs?
|
||||||
|
elif self._is_pointer_to_struct(base_t):
|
||||||
|
expand_members(p, a, v.members, parent_is_ptr_struct=True)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# top-level not an array, but has members
|
||||||
|
if v.members:
|
||||||
|
is_ptr_struct = self._is_pointer_to_struct(v.type_str)
|
||||||
|
expand_members(v.name, v.address, v.members, parent_is_ptr_struct=is_ptr_struct)
|
||||||
|
|
||||||
|
return out
|
||||||
|
|
||||||
|
# -------------------- date candidates (as it was) --------------------
|
||||||
|
|
||||||
|
def date_struct_candidates(self) -> List[Tuple[str,int]]:
|
||||||
|
cands = []
|
||||||
|
for v in self.variables:
|
||||||
|
# top level (if all date fields are present)
|
||||||
|
direct_names = {mm.name for mm in v.members}
|
||||||
|
if DATE_FIELD_SET.issubset(direct_names):
|
||||||
|
cands.append((v.name, v.address))
|
||||||
|
# check first-level members
|
||||||
|
for m in v.members:
|
||||||
|
if m.is_date_struct():
|
||||||
|
cands.append((f"{v.name}.{m.name}", v.address + m.offset))
|
||||||
|
return cands
|
||||||
|
|
||||||
|
|
||||||
|
def get_all_vars_data(self) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
Возвращает вложенную структуру словарей с полными данными для всех переменных и их развернутых членов.
|
||||||
|
Каждый словарь представляет узел в иерархии и содержит:
|
||||||
|
'name' (полный путь), 'address', 'size', 'type', 'kind', 'count', и 'children' (если есть).
|
||||||
|
Логика определения родительского пути теперь использует `split_path` для анализа структуры пути.
|
||||||
|
"""
|
||||||
|
flat_data = self.flattened(max_array_elems=None)
|
||||||
|
|
||||||
|
root_nodes: List[Dict[str, Any]] = []
|
||||||
|
all_nodes_map: Dict[str, Dict[str, Any]] = {}
|
||||||
|
|
||||||
|
for item in flat_data:
|
||||||
|
node_dict = {**item, 'children': []}
|
||||||
|
all_nodes_map[item['name']] = node_dict
|
||||||
|
|
||||||
|
# Вспомогательная функция для определения полного пути родителя с использованием split_path
|
||||||
|
def get_parent_path_using_split(full_path: str) -> Optional[str]:
|
||||||
|
# 1. Используем split_path для получения компонентов пути.
|
||||||
|
components = var_setup.split_path(full_path)
|
||||||
|
|
||||||
|
# Если нет компонентов или только один (верхний уровень, не массивный элемент)
|
||||||
|
if not components or len(components) == 1:
|
||||||
|
# Если компонент один и это не индекс массива (например, "project" или "my_var")
|
||||||
|
# тогда у него нет родителя в этой иерархии.
|
||||||
|
# Если это был бы "my_array[0]" -> components=['my_array', '[0]'], len=2
|
||||||
|
if len(components) == 1 and not components[0].startswith('['):
|
||||||
|
return None
|
||||||
|
elif len(components) == 2 and components[-1].startswith('['): # like "my_array[0]"
|
||||||
|
return components[0] # Return "my_array" as parent
|
||||||
|
else: # Edge cases or malformed, treat as root
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# 2. Определяем, как отрезать "хвост" из оригинальной строки `full_path`, чтобы получить родителя.
|
||||||
|
# Эта логика остаётся похожей на предыдущую, так как `split_path` не включает разделители
|
||||||
|
# и мы должны получить точную строку родительского пути.
|
||||||
|
|
||||||
|
# Находим индекс последнего разделителя '.' или '->'
|
||||||
|
last_dot_idx = full_path.rfind('.')
|
||||||
|
last_arrow_idx = full_path.rfind('->')
|
||||||
|
|
||||||
|
effective_last_sep_idx = -1
|
||||||
|
if last_dot_idx > last_arrow_idx:
|
||||||
|
effective_last_sep_idx = last_dot_idx
|
||||||
|
elif last_arrow_idx != -1:
|
||||||
|
effective_last_sep_idx = last_arrow_idx
|
||||||
|
|
||||||
|
# Находим начало последнего суффикса массива (e.g., '[0]') в оригинальной строке
|
||||||
|
array_suffix_match = re.search(r'(\[[^\]]*\])+$', full_path)
|
||||||
|
array_suffix_start_idx = -1
|
||||||
|
if array_suffix_match:
|
||||||
|
array_suffix_start_idx = array_suffix_match.start()
|
||||||
|
|
||||||
|
# Логика определения родителя:
|
||||||
|
# - Если есть суффикс массива, и он находится после последнего разделителя (или разделителей нет),
|
||||||
|
# то родитель - это часть до суффикса массива. (e.g., 'project.adc[0]' -> 'project.adc')
|
||||||
|
# - Иначе, если есть разделитель, родитель - это часть до последнего разделителя. (e.g., 'project.adc.bus' -> 'project.adc')
|
||||||
|
# - Иначе (ни разделителей, ни суффиксов), это корневой элемент.
|
||||||
|
if array_suffix_start_idx != -1 and (array_suffix_start_idx > effective_last_sep_idx):
|
||||||
|
return full_path[:array_suffix_start_idx]
|
||||||
|
elif effective_last_sep_idx != -1:
|
||||||
|
return full_path[:effective_last_sep_idx]
|
||||||
|
else:
|
||||||
|
return None # Корневой элемент без явного родителя
|
||||||
|
|
||||||
|
# Основная логика get_all_vars_data
|
||||||
|
|
||||||
|
# Заполнение связей "родитель-потомок"
|
||||||
|
for item_name, node_dict in all_nodes_map.items():
|
||||||
|
parent_name = get_parent_path_using_split(item_name) # Используем новую вспомогательную функцию
|
||||||
|
if parent_name and parent_name in all_nodes_map:
|
||||||
|
all_nodes_map[parent_name]['children'].append(node_dict)
|
||||||
|
else:
|
||||||
|
root_nodes.append(node_dict)
|
||||||
|
|
||||||
|
# Сортируем корневые узлы и их детей рекурсивно по имени
|
||||||
|
def sort_nodes(nodes_list: List[Dict[str, Any]]):
|
||||||
|
nodes_list.sort(key=lambda x: x['name'])
|
||||||
|
for node in nodes_list:
|
||||||
|
if node['children']:
|
||||||
|
sort_nodes(node['children'])
|
||||||
|
|
||||||
|
sort_nodes(root_nodes)
|
||||||
|
|
||||||
|
return root_nodes
|
||||||
|
|
223
Src/csv_logger.py
Normal file
223
Src/csv_logger.py
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
import csv
|
||||||
|
import numbers
|
||||||
|
import time
|
||||||
|
from datetime import datetime
|
||||||
|
from PySide2 import QtWidgets
|
||||||
|
|
||||||
|
|
||||||
|
class CsvLogger:
|
||||||
|
"""
|
||||||
|
Логгер, совместимый по формату с C-реализацией CSV_AddTitlesLine / CSV_AddLogLine.
|
||||||
|
|
||||||
|
Публичный API сохранён:
|
||||||
|
set_titles(varnames)
|
||||||
|
set_value(timestamp, varname, varvalue)
|
||||||
|
select_file(parent=None) -> bool
|
||||||
|
write_to_csv()
|
||||||
|
|
||||||
|
Использование:
|
||||||
|
1) set_titles([...])
|
||||||
|
2) многократно set_value(ts, name, value)
|
||||||
|
3) select_file() (по желанию)
|
||||||
|
4) write_to_csv()
|
||||||
|
"""
|
||||||
|
def __init__(self, filename="log.csv", delimiter=';'):
|
||||||
|
self._filename = filename
|
||||||
|
self._delimiter = delimiter
|
||||||
|
|
||||||
|
# Пользовательские заголовки
|
||||||
|
self.variable_names_ordered = []
|
||||||
|
# Полные заголовки CSV (Ticks(X), Ticks(Y), Time(Y), ...)
|
||||||
|
self.headers = ['t'] # до вызова set_titles placeholder
|
||||||
|
|
||||||
|
# Данные: {timestamp_key: {varname: value, ...}}
|
||||||
|
# timestamp_key = то, что передано в set_value (float/int/etc)
|
||||||
|
self.data_rows = {}
|
||||||
|
|
||||||
|
# Внутренние структуры для генерации CSV-формата С
|
||||||
|
self._row_wall_dt = {} # {timestamp_key: datetime при первой записи}
|
||||||
|
self._base_ts = None # timestamp_key первой строки (число)
|
||||||
|
self._base_ts_val = 0.0 # float значение первой строки (для delta)
|
||||||
|
self._tick_x_start = 0 # начальный тик (можно менять вручную при необходимости)
|
||||||
|
|
||||||
|
# ---- Свойства ----
|
||||||
|
@property
|
||||||
|
def filename(self):
|
||||||
|
return self._filename
|
||||||
|
|
||||||
|
# ---- Публичные методы ----
|
||||||
|
def set_titles(self, varnames):
|
||||||
|
"""
|
||||||
|
Устанавливает имена переменных.
|
||||||
|
Формирует полные заголовки CSV в формате С-лога.
|
||||||
|
"""
|
||||||
|
if not isinstance(varnames, list):
|
||||||
|
raise TypeError("Varnames must be a list of strings.")
|
||||||
|
if not all(isinstance(name, str) for name in varnames):
|
||||||
|
raise ValueError("All variable names must be strings.")
|
||||||
|
|
||||||
|
self.variable_names_ordered = varnames
|
||||||
|
self.headers = ["Ticks(X)", "Ticks(Y)", "Time(Y)"] + self.variable_names_ordered
|
||||||
|
|
||||||
|
# Сброс данных (структура изменилась)
|
||||||
|
self.data_rows.clear()
|
||||||
|
self._row_wall_dt.clear()
|
||||||
|
self._base_ts = None
|
||||||
|
self._base_ts_val = 0.0
|
||||||
|
|
||||||
|
|
||||||
|
def set_value(self, timestamp, varname, varvalue):
|
||||||
|
"""
|
||||||
|
Установить ОДНО значение в ОДНУ колонку для заданного timestamp’а.
|
||||||
|
timestamp — float секунд с эпохи (time.time()).
|
||||||
|
"""
|
||||||
|
if varname not in self.variable_names_ordered:
|
||||||
|
return # игнор, как у тебя было
|
||||||
|
|
||||||
|
# Новая строка?
|
||||||
|
if timestamp not in self.data_rows:
|
||||||
|
# Инициализируем поля переменных значением None
|
||||||
|
self.data_rows[timestamp] = {vn: None for vn in self.variable_names_ordered}
|
||||||
|
|
||||||
|
# Дата/время строки из ПЕРЕДАННОГО timestamp (а не datetime.now()!)
|
||||||
|
try:
|
||||||
|
ts_float = float(timestamp)
|
||||||
|
except Exception:
|
||||||
|
# если какая-то дичь прилетела, пусть будет 0 (эпоха) чтобы не упасть
|
||||||
|
ts_float = 0.0
|
||||||
|
self._row_wall_dt[timestamp] = datetime.fromtimestamp(ts_float)
|
||||||
|
|
||||||
|
# База для расчёта Ticks(Y) — первая строка
|
||||||
|
if self._base_ts is None:
|
||||||
|
self._base_ts = timestamp
|
||||||
|
self._base_ts_val = ts_float
|
||||||
|
|
||||||
|
# Записываем значение
|
||||||
|
self.data_rows[timestamp][varname] = varvalue
|
||||||
|
|
||||||
|
def select_file(self, parent=None) -> bool:
|
||||||
|
"""
|
||||||
|
Диалог выбора файла.
|
||||||
|
"""
|
||||||
|
options = QtWidgets.QFileDialog.Options()
|
||||||
|
filename, _ = QtWidgets.QFileDialog.getSaveFileName(
|
||||||
|
parent,
|
||||||
|
"Сохранить данные CSV",
|
||||||
|
self._filename,
|
||||||
|
"CSV Files (*.csv);;All Files (*)",
|
||||||
|
options=options
|
||||||
|
)
|
||||||
|
if filename:
|
||||||
|
if not filename.lower().endswith('.csv'):
|
||||||
|
filename += '.csv'
|
||||||
|
self._filename = filename
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def write_to_csv(self):
|
||||||
|
"""
|
||||||
|
Формирует CSV в формате C:
|
||||||
|
Ticks(X);Ticks(Y);Time(Y);Var1;Var2;...
|
||||||
|
0;0,000000;22/07/2025 13:45:12:0123;...;...
|
||||||
|
|
||||||
|
Правила значений:
|
||||||
|
- Тик X: автоинкремент от 0 (или self._tick_x_start) по порядку сортировки timestamp.
|
||||||
|
- Ticks(Y): дельта (секунды,микросекунды) между текущим timestamp и первым timestamp.
|
||||||
|
- Time(Y): wallclock строки (datetime.now() при первом появлении timestamp).
|
||||||
|
- Значение < 0 -> пустая ячейка (как if(raw_data[i] >= 0) else ;)
|
||||||
|
- None -> пустая ячейка.
|
||||||
|
"""
|
||||||
|
if len(self.headers) <= 3: # только служебные поля без переменных
|
||||||
|
print("Ошибка: Заголовки не установлены или не содержат переменных. Вызовите set_titles() перед записью.")
|
||||||
|
return
|
||||||
|
if not self._filename:
|
||||||
|
print("Ошибка: Имя файла не определено. select_file() или задайте при инициализации.")
|
||||||
|
return
|
||||||
|
if not self.data_rows:
|
||||||
|
print("Предупреждение: Нет данных для записи.")
|
||||||
|
# всё равно создадим файл с одними заголовками
|
||||||
|
try:
|
||||||
|
with open(self._filename, 'w', newline='', encoding='utf-8') as csvfile:
|
||||||
|
# QUOTE_NONE + escapechar для чистого формата без кавычек (как в С-строке)
|
||||||
|
writer = csv.writer(
|
||||||
|
csvfile,
|
||||||
|
delimiter=self._delimiter,
|
||||||
|
quoting=csv.QUOTE_NONE,
|
||||||
|
escapechar='\\',
|
||||||
|
lineterminator='\r\n'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Пишем заголовки
|
||||||
|
writer.writerow(self.headers)
|
||||||
|
|
||||||
|
if self.data_rows:
|
||||||
|
sorted_ts = sorted(self.data_rows.keys(), key=self._ts_sort_key)
|
||||||
|
# убедимся, что база была зафиксирована
|
||||||
|
if self._base_ts is None:
|
||||||
|
self._base_ts = sorted_ts[0]
|
||||||
|
self._base_ts_val = self._coerce_ts_to_float(self._base_ts)
|
||||||
|
|
||||||
|
tick_x = self._tick_x_start
|
||||||
|
for ts in sorted_ts:
|
||||||
|
row_dict = self.data_rows[ts]
|
||||||
|
# delta по timestamp
|
||||||
|
cur_ts_val = self._coerce_ts_to_float(ts)
|
||||||
|
delta_us = int(round((cur_ts_val - self._base_ts_val) * 1_000_000))
|
||||||
|
if delta_us < 0:
|
||||||
|
delta_us = 0 # защита
|
||||||
|
|
||||||
|
seconds = delta_us // 1_000_000
|
||||||
|
micros = delta_us % 1_000_000
|
||||||
|
|
||||||
|
# wallclock строки
|
||||||
|
dt = self._row_wall_dt.get(ts, datetime.now())
|
||||||
|
# Формат DD/MM/YYYY HH:MM:SS:мммм (4 цифры ms, как в C: us/1000)
|
||||||
|
time_str = dt.strftime("%d/%m/%Y %H:%M:%S") + f":{dt.microsecond // 1000:04d}"
|
||||||
|
|
||||||
|
# Значения
|
||||||
|
row_vals = []
|
||||||
|
for vn in self.variable_names_ordered:
|
||||||
|
v = row_dict.get(vn)
|
||||||
|
if v is None:
|
||||||
|
row_vals.append("") # нет данных
|
||||||
|
else:
|
||||||
|
# если числовое и <0 -> пусто (как в C: если raw_data[i] >= 0 else ;)
|
||||||
|
if isinstance(v, numbers.Number) and v < 0:
|
||||||
|
row_vals.append("")
|
||||||
|
else:
|
||||||
|
row_vals.append(v)
|
||||||
|
|
||||||
|
csv_row = [tick_x, f"{seconds},{micros:06d}", time_str] + row_vals
|
||||||
|
writer.writerow(csv_row)
|
||||||
|
tick_x += 1
|
||||||
|
|
||||||
|
print(f"Данные успешно записаны в '{self._filename}'")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Ошибка при записи в файл '{self._filename}': {e}")
|
||||||
|
|
||||||
|
# ---- Вспомогательные ----
|
||||||
|
def _coerce_ts_to_float(self, ts):
|
||||||
|
"""
|
||||||
|
Пробуем привести переданный timestamp к float.
|
||||||
|
Разрешаем int/float/str, остальное -> индекс по порядку (0).
|
||||||
|
"""
|
||||||
|
if isinstance(ts, numbers.Number):
|
||||||
|
return float(ts)
|
||||||
|
try:
|
||||||
|
return float(ts)
|
||||||
|
except Exception:
|
||||||
|
# fallback: нечисловой ключ -> используем порядковый индекс
|
||||||
|
# (таких почти не должно быть, но на всякий)
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
def _ts_sort_key(self, ts):
|
||||||
|
"""
|
||||||
|
Ключ сортировки timestamp’ов — сначала попытка float, потом str.
|
||||||
|
"""
|
||||||
|
if isinstance(ts, numbers.Number):
|
||||||
|
return (0, float(ts))
|
||||||
|
try:
|
||||||
|
return (0, float(ts))
|
||||||
|
except Exception:
|
||||||
|
return (1, str(ts))
|
@ -12,9 +12,10 @@ from xml.dom import minidom
|
|||||||
import myXML
|
import myXML
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
|
shortnameSize = 10
|
||||||
|
|
||||||
# === Словарь соответствия типов XML → DebugVarType_t ===
|
# === Словарь соответствия типов XML → DebugVarType_t ===
|
||||||
type_map = dict([
|
type_map_tms = dict([
|
||||||
*[(k, 'pt_int8') for k in ('signed char', 'char')],
|
*[(k, 'pt_int8') for k in ('signed char', 'char')],
|
||||||
*[(k, 'pt_int16') for k in ('int', 'int16', 'short')],
|
*[(k, 'pt_int16') for k in ('int', 'int16', 'short')],
|
||||||
*[(k, 'pt_int32') for k in ('long', 'int32', '_iqx')],
|
*[(k, 'pt_int32') for k in ('long', 'int32', '_iqx')],
|
||||||
@ -52,6 +53,150 @@ type_map = dict([
|
|||||||
('struct[]', 'pt_arr_struct'),
|
('struct[]', 'pt_arr_struct'),
|
||||||
('union[]', 'pt_arr_union'),
|
('union[]', 'pt_arr_union'),
|
||||||
])
|
])
|
||||||
|
# === Словарь соответствия типов XML → DebugVarType_t ===
|
||||||
|
type_map_stm32 = dict([
|
||||||
|
|
||||||
|
*[(k, 'pt_int8') for k in (
|
||||||
|
'int8_t', 'signed char', 'char'
|
||||||
|
)],
|
||||||
|
|
||||||
|
# --- 8-bit unsigned ---
|
||||||
|
*[(k, 'pt_uint8') for k in (
|
||||||
|
'uint8_t', 'unsigned char'
|
||||||
|
)],
|
||||||
|
|
||||||
|
# --- 16-bit signed ---
|
||||||
|
*[(k, 'pt_int16') for k in (
|
||||||
|
'int16_t', 'short', 'short int', 'signed short', 'signed short int'
|
||||||
|
)],
|
||||||
|
|
||||||
|
# --- 16-bit unsigned ---
|
||||||
|
*[(k, 'pt_uint16') for k in (
|
||||||
|
'uint16_t', 'unsigned short', 'unsigned short int'
|
||||||
|
)],
|
||||||
|
|
||||||
|
# --- 32-bit signed ---
|
||||||
|
*[(k, 'pt_int32') for k in (
|
||||||
|
'int32_t', 'int', 'signed', 'signed int'
|
||||||
|
)],
|
||||||
|
|
||||||
|
# --- 32-bit unsigned ---
|
||||||
|
*[(k, 'pt_uint32') for k in (
|
||||||
|
'uint32_t', 'unsigned', 'unsigned int'
|
||||||
|
)],
|
||||||
|
|
||||||
|
# --- 64-bit signed ---
|
||||||
|
*[(k, 'pt_int64') for k in (
|
||||||
|
'int64_t', 'long long', 'signed long long', 'signed long long int'
|
||||||
|
)],
|
||||||
|
|
||||||
|
# --- 64-bit unsigned ---
|
||||||
|
*[(k, 'pt_uint64') for k in (
|
||||||
|
'uint64_t', 'unsigned long long', 'unsigned long long int'
|
||||||
|
)],
|
||||||
|
|
||||||
|
# --- Float ---
|
||||||
|
*[(k, 'pt_float') for k in (
|
||||||
|
'float', 'float32_t'
|
||||||
|
)],
|
||||||
|
|
||||||
|
# --- Struct and Union ---
|
||||||
|
('struct', 'pt_struct'),
|
||||||
|
('union', 'pt_union'),
|
||||||
|
('struct*', 'pt_ptr_struct'),
|
||||||
|
('union*', 'pt_ptr_union'),
|
||||||
|
('struct[]', 'pt_arr_struct'),
|
||||||
|
('union[]', 'pt_arr_union'),
|
||||||
|
|
||||||
|
# === POINTERS ===
|
||||||
|
|
||||||
|
# 8-bit
|
||||||
|
*[(k, 'pt_ptr_int8') for k in (
|
||||||
|
'int8_t*', 'signed char*', 'char*'
|
||||||
|
)],
|
||||||
|
*[(k, 'pt_ptr_uint8') for k in (
|
||||||
|
'uint8_t*', 'unsigned char*'
|
||||||
|
)],
|
||||||
|
|
||||||
|
# 16-bit
|
||||||
|
*[(k, 'pt_ptr_int16') for k in (
|
||||||
|
'int16_t*', 'short*', 'short int*', 'signed short*', 'signed short int*'
|
||||||
|
)],
|
||||||
|
*[(k, 'pt_ptr_uint16') for k in (
|
||||||
|
'uint16_t*', 'unsigned short*', 'unsigned short int*'
|
||||||
|
)],
|
||||||
|
|
||||||
|
# 32-bit
|
||||||
|
*[(k, 'pt_ptr_int32') for k in (
|
||||||
|
'int32_t*', 'int*', 'signed*', 'signed int*'
|
||||||
|
)],
|
||||||
|
*[(k, 'pt_ptr_uint32') for k in (
|
||||||
|
'uint32_t*', 'unsigned*', 'unsigned int*'
|
||||||
|
)],
|
||||||
|
|
||||||
|
# 64-bit
|
||||||
|
*[(k, 'pt_ptr_int64') for k in (
|
||||||
|
'int64_t*', 'long long*', 'signed long long*', 'signed long long int*'
|
||||||
|
)],
|
||||||
|
*[(k, 'pt_ptr_uint64') for k in (
|
||||||
|
'uint64_t*', 'unsigned long long*', 'unsigned long long int*'
|
||||||
|
)],
|
||||||
|
|
||||||
|
# float*
|
||||||
|
*[(k, 'pt_ptr_float') for k in (
|
||||||
|
'float*', 'float32_t*'
|
||||||
|
)],
|
||||||
|
|
||||||
|
# === ARRAYS ===
|
||||||
|
|
||||||
|
# 8-bit
|
||||||
|
*[(k, 'pt_arr_int8') for k in (
|
||||||
|
'int8_t[]', 'signed char[]', 'char[]'
|
||||||
|
)],
|
||||||
|
*[(k, 'pt_arr_uint8') for k in (
|
||||||
|
'uint8_t[]', 'unsigned char[]'
|
||||||
|
)],
|
||||||
|
|
||||||
|
# 16-bit
|
||||||
|
*[(k, 'pt_arr_int16') for k in (
|
||||||
|
'int16_t[]', 'short[]', 'short int[]', 'signed short[]', 'signed short int[]'
|
||||||
|
)],
|
||||||
|
*[(k, 'pt_arr_uint16') for k in (
|
||||||
|
'uint16_t[]', 'unsigned short[]', 'unsigned short int[]'
|
||||||
|
)],
|
||||||
|
|
||||||
|
# 32-bit
|
||||||
|
*[(k, 'pt_arr_int32') for k in (
|
||||||
|
'int32_t[]', 'int[]', 'signed[]', 'signed int[]'
|
||||||
|
)],
|
||||||
|
*[(k, 'pt_arr_uint32') for k in (
|
||||||
|
'uint32_t[]', 'unsigned[]', 'unsigned int[]'
|
||||||
|
)],
|
||||||
|
|
||||||
|
# 64-bit
|
||||||
|
*[(k, 'pt_arr_int64') for k in (
|
||||||
|
'int64_t[]', 'long long[]', 'signed long long[]', 'signed long long int[]'
|
||||||
|
)],
|
||||||
|
*[(k, 'pt_arr_uint64') for k in (
|
||||||
|
'uint64_t[]', 'unsigned long long[]', 'unsigned long long int[]'
|
||||||
|
)],
|
||||||
|
|
||||||
|
# float[]
|
||||||
|
*[(k, 'pt_arr_float') for k in (
|
||||||
|
'float[]', 'float32_t[]'
|
||||||
|
)],
|
||||||
|
])
|
||||||
|
type_map = type_map_tms
|
||||||
|
stm_flag_global = 0
|
||||||
|
def choose_type_map(stm_flag):
|
||||||
|
global type_map # объявляем, что будем менять глобальную переменную
|
||||||
|
global stm_flag_global # объявляем, что будем менять глобальную переменную
|
||||||
|
if stm_flag:
|
||||||
|
type_map = type_map_stm32
|
||||||
|
stm_flag_global = 1
|
||||||
|
else:
|
||||||
|
type_map = type_map_tms
|
||||||
|
stm_flag_global = 0
|
||||||
|
|
||||||
def map_type_to_pt(typename, varname=None, typedef_map=None):
|
def map_type_to_pt(typename, varname=None, typedef_map=None):
|
||||||
typename_orig = typename.strip()
|
typename_orig = typename.strip()
|
||||||
@ -140,15 +285,20 @@ def add_new_vars_to_xml(proj_path, xml_rel_path, output_path):
|
|||||||
Возвращает True если что-то добавлено и XML перезаписан, иначе False.
|
Возвращает True если что-то добавлено и XML перезаписан, иначе False.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
pattern = re.compile(
|
||||||
|
r'{\s*\(uint8_t\s*\*\)\s*&([a-zA-Z_][a-zA-Z0-9_]*(?:\[.*?\])?(?:(?:\.|->)[a-zA-Z_][a-zA-Z0-9_]*(?:\[.*?\])?)*)\s*,\s*'
|
||||||
|
r'(pt_\w+)\s*,\s*'
|
||||||
|
r'(t?_?iq\w+)\s*,\s*'
|
||||||
|
r'(t?_?iq\w+)\s*,\s*'
|
||||||
|
r'"([^"]+)"'
|
||||||
|
)
|
||||||
# Считываем существующие переменные
|
# Считываем существующие переменные
|
||||||
parsed_vars = {}
|
parsed_vars = {}
|
||||||
if os.path.isfile(output_path):
|
if os.path.isfile(output_path):
|
||||||
with open(output_path, 'r', encoding='utf-8', errors='ignore') as f:
|
with open(output_path, 'r', encoding='utf-8', errors='ignore') as f:
|
||||||
for line in f:
|
for line in f:
|
||||||
# {(char *)&some.deep.var.name , pt_uint16 , t_iq15 , "ShortName"},
|
# {(uint8_t *)&some.deep.var.name , pt_uint16 , t_iq15 , t_iq10, "ShortName"},
|
||||||
m = re.match(
|
m = pattern.search(line)
|
||||||
r'{\s*\(char\s*\*\)\s*&([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*)\s*,\s*(pt_\w+)\s*,\s*(t?iq_\w+)\s*,\s*"([^"]+)"',
|
|
||||||
line)
|
|
||||||
if m:
|
if m:
|
||||||
full_varname = m.group(1) # e.g., some.deep.var.name
|
full_varname = m.group(1) # e.g., some.deep.var.name
|
||||||
pt_type = m.group(2)
|
pt_type = m.group(2)
|
||||||
@ -285,12 +435,27 @@ def read_vars_from_xml(proj_path, xml_rel_path):
|
|||||||
return unique_vars, include_files, vars_need_extern
|
return unique_vars, include_files, vars_need_extern
|
||||||
|
|
||||||
|
|
||||||
|
def read_file_try_encodings(filepath):
|
||||||
|
if not os.path.isfile(filepath):
|
||||||
|
# Файл не существует — просто вернуть пустую строку или None
|
||||||
|
return "", None
|
||||||
|
for enc in ['utf-8', 'cp1251']:
|
||||||
|
try:
|
||||||
|
with open(filepath, 'r', encoding=enc) as f:
|
||||||
|
content = f.read()
|
||||||
|
return content, enc
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
continue
|
||||||
|
raise UnicodeDecodeError(f"Не удалось прочитать файл {filepath} с кодировками utf-8 и cp1251")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def generate_vars_file(proj_path, xml_path, output_dir):
|
def generate_vars_file(proj_path, xml_path, output_dir):
|
||||||
output_dir = os.path.join(proj_path, output_dir)
|
output_dir = os.path.join(proj_path, output_dir)
|
||||||
os.makedirs(output_dir, exist_ok=True)
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
output_path = os.path.join(output_dir, 'debug_vars.c')
|
output_path = os.path.join(output_dir, 'debug_vars.c')
|
||||||
|
LIBC_path = os.path.join(output_dir, 'debug_tools.c')
|
||||||
|
LIBH_path = os.path.join(output_dir, 'debug_tools.h')
|
||||||
|
|
||||||
|
|
||||||
# Запись новых переменных для в XML
|
# Запись новых переменных для в XML
|
||||||
@ -338,7 +503,7 @@ def generate_vars_file(proj_path, xml_path, output_dir):
|
|||||||
# Дополнительные поля, например комментарий
|
# Дополнительные поля, например комментарий
|
||||||
comment = info.get("comment", "")
|
comment = info.get("comment", "")
|
||||||
short_name = info.get("shortname", f'"{vname}"')
|
short_name = info.get("shortname", f'"{vname}"')
|
||||||
short_trimmed = short_name[:10] # ограничиваем длину до 10
|
short_trimmed = short_name[:shortnameSize] # ограничиваем длину до 10
|
||||||
|
|
||||||
if pt_type not in ('pt_struct', 'pt_union'):
|
if pt_type not in ('pt_struct', 'pt_union'):
|
||||||
f_name = f'{vname},'
|
f_name = f'{vname},'
|
||||||
@ -348,7 +513,7 @@ def generate_vars_file(proj_path, xml_path, output_dir):
|
|||||||
f_short_name = f'"{short_trimmed}"' # оборачиваем в кавычки
|
f_short_name = f'"{short_trimmed}"' # оборачиваем в кавычки
|
||||||
# Добавим комментарий после записи, если он есть
|
# Добавим комментарий после записи, если он есть
|
||||||
comment_str = f' // {comment}' if comment else ''
|
comment_str = f' // {comment}' if comment else ''
|
||||||
line = f'{{(char *)&{f_name:<57} {f_type:<15} {f_iq:<15} {f_ret_iq:<15} {f_short_name:<21}}}, \\{comment_str}'
|
line = f'{{(uint8_t *)&{f_name:<58} {f_type:<15} {f_iq:<15} {f_ret_iq:<15} {f_short_name:<21}}}, \\{comment_str}'
|
||||||
new_debug_vars[vname] = line
|
new_debug_vars[vname] = line
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@ -394,14 +559,19 @@ def generate_vars_file(proj_path, xml_path, output_dir):
|
|||||||
|
|
||||||
out_lines.append(f'\n\n// Определение массива с указателями на переменные для отладки')
|
out_lines.append(f'\n\n// Определение массива с указателями на переменные для отладки')
|
||||||
out_lines.append(f'int DebugVar_Qnt = {len(all_debug_lines)};')
|
out_lines.append(f'int DebugVar_Qnt = {len(all_debug_lines)};')
|
||||||
out_lines.append('#pragma DATA_SECTION(dbg_vars,".dbgvar_info")')
|
if stm_flag_global == 0:
|
||||||
|
out_lines.append('#pragma DATA_SECTION(dbg_vars,".dbgvar_info")')
|
||||||
|
out_lines.append('// pointer_type iq_type return_iq_type short_name')
|
||||||
out_lines.append('DebugVar_t dbg_vars[] = {\\')
|
out_lines.append('DebugVar_t dbg_vars[] = {\\')
|
||||||
out_lines.extend(all_debug_lines)
|
out_lines.extend(all_debug_lines)
|
||||||
out_lines.append('};')
|
out_lines.append('};')
|
||||||
out_lines.append('')
|
out_lines.append('')
|
||||||
# Выберем кодировку для записи файла
|
# Выберем кодировку для записи файла
|
||||||
# Если встречается несколько, возьмем первую из set
|
# Если встречается несколько, возьмем первую из set
|
||||||
enc_to_write = 'cp1251'
|
if stm_flag_global == 0:
|
||||||
|
enc_to_write = 'cp1251'
|
||||||
|
else:
|
||||||
|
enc_to_write = 'utf-8'
|
||||||
|
|
||||||
#print("== GLOBAL VARS FOUND ==")
|
#print("== GLOBAL VARS FOUND ==")
|
||||||
#for vname, (vtype, path) in vars_in_c.items():
|
#for vname, (vtype, path) in vars_in_c.items():
|
||||||
@ -411,6 +581,16 @@ def generate_vars_file(proj_path, xml_path, output_dir):
|
|||||||
with open(output_path, 'w', encoding=enc_to_write) as f:
|
with open(output_path, 'w', encoding=enc_to_write) as f:
|
||||||
f.write('\n'.join(out_lines))
|
f.write('\n'.join(out_lines))
|
||||||
|
|
||||||
|
if os.path.isfile(LIBC_path):
|
||||||
|
libc_code, _ = read_file_try_encodings(LIBC_path)
|
||||||
|
with open(LIBC_path, 'w', encoding=enc_to_write) as f:
|
||||||
|
f.write(libc_code)
|
||||||
|
|
||||||
|
if os.path.isfile(LIBH_path):
|
||||||
|
libh_code, _ = read_file_try_encodings(LIBH_path)
|
||||||
|
with open(LIBH_path, 'w', encoding=enc_to_write) as f:
|
||||||
|
f.write(libh_code)
|
||||||
|
|
||||||
print(f'Файл debug_vars.c сгенерирован в кодировке, переменных: {len(all_debug_lines)}')
|
print(f'Файл debug_vars.c сгенерирован в кодировке, переменных: {len(all_debug_lines)}')
|
||||||
|
|
||||||
|
|
||||||
@ -472,16 +652,17 @@ Usage example:
|
|||||||
print(f"Error: Project path '{proj_path}' не является директорией или не существует.")
|
print(f"Error: Project path '{proj_path}' не является директорией или не существует.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
generate_vars_file(proj_path, xml_path_rel, output_dir_rel)
|
generate_vars_file(proj_path, xml_path_rel, output_dir_rel, 0)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
||||||
def run_generate(proj_path, xml_path, output_dir):
|
def run_generate(proj_path, xml_path, output_dir, shortname_size):
|
||||||
import os
|
import os
|
||||||
|
global shortnameSize
|
||||||
|
shortnameSize = shortname_size
|
||||||
# Normalize absolute paths
|
# Normalize absolute paths
|
||||||
proj_path = os.path.abspath(proj_path)
|
proj_path = os.path.abspath(proj_path)
|
||||||
xml_path_abs = os.path.abspath(xml_path)
|
xml_path_abs = os.path.abspath(xml_path)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
from lxml import etree as ET
|
||||||
|
|
||||||
|
|
||||||
def strip_single_line_comments(code):
|
def strip_single_line_comments(code):
|
||||||
@ -66,89 +67,176 @@ def find_all_includes_recursive(c_files, include_dirs, processed_files=None):
|
|||||||
return include_files
|
return include_files
|
||||||
|
|
||||||
|
|
||||||
|
def parse_objects_list(objects_list_path, project_root):
|
||||||
|
c_files = []
|
||||||
|
include_dirs = set()
|
||||||
|
|
||||||
|
if not os.path.isfile(objects_list_path):
|
||||||
|
return c_files, include_dirs
|
||||||
|
|
||||||
|
with open(objects_list_path, 'r', encoding='utf-8') as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
line = line.strip().strip('"').replace("\\", "/")
|
||||||
|
if line.endswith(".o"):
|
||||||
|
c_file = re.sub(r"\.o$", ".c", line)
|
||||||
|
abs_path = os.path.normpath(os.path.join(project_root, c_file))
|
||||||
|
if os.path.isfile(abs_path):
|
||||||
|
if not any(x in abs_path for x in ["DebugTools", "v120", "v100"]):
|
||||||
|
c_files.append(abs_path)
|
||||||
|
include_dirs.add(os.path.dirname(abs_path))
|
||||||
|
|
||||||
|
return c_files, include_dirs
|
||||||
|
|
||||||
|
|
||||||
|
def parse_uvprojx(uvprojx_path):
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
import os
|
||||||
|
|
||||||
|
tree = ET.parse(uvprojx_path)
|
||||||
|
root = tree.getroot()
|
||||||
|
|
||||||
|
project_dir = os.path.dirname(os.path.abspath(uvprojx_path))
|
||||||
|
|
||||||
|
c_files = []
|
||||||
|
include_dirs = set()
|
||||||
|
defines = set()
|
||||||
|
|
||||||
|
# Найдём C-файлы и директории
|
||||||
|
for file_elem in root.findall(".//FilePath"):
|
||||||
|
file_path = file_elem.text
|
||||||
|
if file_path:
|
||||||
|
abs_path = os.path.normpath(os.path.join(project_dir, file_path))
|
||||||
|
if os.path.isfile(abs_path):
|
||||||
|
if abs_path.endswith(".c"):
|
||||||
|
c_files.append(abs_path)
|
||||||
|
include_dirs.add(os.path.dirname(abs_path))
|
||||||
|
|
||||||
|
# Включаем IncludePath
|
||||||
|
for inc_path_elem in root.findall(".//IncludePath"):
|
||||||
|
path_text = inc_path_elem.text
|
||||||
|
if path_text:
|
||||||
|
paths = path_text.split(';')
|
||||||
|
for p in paths:
|
||||||
|
p = p.strip()
|
||||||
|
if p:
|
||||||
|
abs_inc_path = os.path.normpath(os.path.join(project_dir, p))
|
||||||
|
if os.path.isdir(abs_inc_path):
|
||||||
|
include_dirs.add(abs_inc_path)
|
||||||
|
|
||||||
|
# Добавим <Define>
|
||||||
|
for define_elem in root.findall(".//Define"):
|
||||||
|
def_text = define_elem.text
|
||||||
|
if def_text:
|
||||||
|
for d in def_text.split(','):
|
||||||
|
d = d.strip()
|
||||||
|
if d:
|
||||||
|
defines.add(d)
|
||||||
|
|
||||||
|
h_files = find_all_includes_recursive(c_files, include_dirs)
|
||||||
|
|
||||||
|
return sorted(c_files), sorted(h_files), sorted(include_dirs), sorted(defines)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def parse_makefile(makefile_path, proj_path):
|
def parse_makefile(makefile_path, proj_path):
|
||||||
makefile_dir = os.path.dirname(makefile_path)
|
import os
|
||||||
project_root = proj_path
|
import re
|
||||||
|
|
||||||
|
project_root = os.path.abspath(proj_path)
|
||||||
|
c_files = []
|
||||||
|
include_dirs = set()
|
||||||
|
defines = [] # Заглушка: нет define-параметров из Makefile
|
||||||
|
|
||||||
with open(makefile_path, 'r', encoding='utf-8') as f:
|
with open(makefile_path, 'r', encoding='utf-8') as f:
|
||||||
lines = f.readlines()
|
lines = f.readlines()
|
||||||
|
|
||||||
objs_lines = []
|
raw_entries = []
|
||||||
collecting = False
|
collecting = False
|
||||||
|
|
||||||
for line in lines:
|
for line in lines:
|
||||||
stripped = line.strip()
|
stripped = line.strip()
|
||||||
if stripped.startswith("ORDERED_OBJS") and "+=" in stripped:
|
|
||||||
parts = stripped.split("\\")
|
if (("ORDERED_OBJS" in stripped or "C_SOURCES" in stripped) and ("+=" in stripped or "=" in stripped)):
|
||||||
first_part = parts[0]
|
|
||||||
idx = first_part.find("+=")
|
|
||||||
tail = first_part[idx+2:].strip()
|
|
||||||
if tail:
|
|
||||||
objs_lines.append(tail)
|
|
||||||
collecting = True
|
collecting = True
|
||||||
if len(parts) > 1:
|
|
||||||
for p in parts[1:]:
|
|
||||||
p = p.strip()
|
|
||||||
if p:
|
|
||||||
objs_lines.append(p)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if collecting:
|
if collecting:
|
||||||
if stripped.endswith("\\"):
|
line_clean = stripped.rstrip("\\").strip()
|
||||||
objs_lines.append(stripped[:-1].strip())
|
if line_clean:
|
||||||
else:
|
line_clean = re.sub(r"\$\([^)]+\)", "", line_clean)
|
||||||
objs_lines.append(stripped)
|
line_clean = re.sub(r"\$\{[^}]+\}", "", line_clean)
|
||||||
|
raw_entries.append(line_clean)
|
||||||
|
|
||||||
|
if not stripped.endswith("\\"):
|
||||||
collecting = False
|
collecting = False
|
||||||
|
|
||||||
objs_str = ' '.join(objs_lines)
|
for entry in raw_entries:
|
||||||
|
for token in entry.split():
|
||||||
|
token = token.strip('"')
|
||||||
|
if not token:
|
||||||
|
continue
|
||||||
|
|
||||||
objs_str = re.sub(r"\$\([^)]+\)", "", objs_str)
|
token = token.replace("\\", "/")
|
||||||
|
|
||||||
objs = []
|
if token.endswith(".obj"):
|
||||||
for part in objs_str.split():
|
token = re.sub(r"\.obj$", ".c", token)
|
||||||
part = part.strip()
|
elif token.endswith(".o"):
|
||||||
if part.startswith('"') and part.endswith('"'):
|
token = re.sub(r"\.o$", ".c", token)
|
||||||
part = part[1:-1]
|
|
||||||
if part:
|
|
||||||
objs.append(part)
|
|
||||||
|
|
||||||
c_files = []
|
if token.endswith(".c"):
|
||||||
include_dirs = set()
|
abs_path = os.path.normpath(os.path.join(project_root, token))
|
||||||
|
if os.path.isfile(abs_path):
|
||||||
|
if not any(x in abs_path for x in ["DebugTools", "v120", "v100"]):
|
||||||
|
c_files.append(abs_path)
|
||||||
|
include_dirs.add(os.path.dirname(abs_path))
|
||||||
|
|
||||||
for obj_path in objs:
|
if not c_files:
|
||||||
if "DebugTools" in obj_path:
|
makefile_dir = os.path.dirname(os.path.abspath(makefile_path))
|
||||||
continue
|
objects_list_path = os.path.join(makefile_dir, "objects.list")
|
||||||
if "v120" in obj_path:
|
c_from_objects, inc_from_objects = parse_objects_list(objects_list_path, project_root)
|
||||||
continue
|
c_files.extend(c_from_objects)
|
||||||
if "v100" in obj_path:
|
include_dirs.update(inc_from_objects)
|
||||||
continue
|
|
||||||
|
|
||||||
if obj_path.startswith("Debug\\") or obj_path.startswith("Debug/"):
|
for line in lines:
|
||||||
rel_path = obj_path.replace("Debug\\", "Src\\").replace("Debug/", "Src/")
|
if "-I" in line or "C_INCLUDES" in line:
|
||||||
else:
|
matches = re.findall(r"-I\s*([^\s\\]+)", line)
|
||||||
rel_path = obj_path
|
for match in matches:
|
||||||
|
match = match.strip('"').replace("\\", "/")
|
||||||
|
abs_include = os.path.normpath(os.path.join(project_root, match))
|
||||||
|
if os.path.isdir(abs_include):
|
||||||
|
include_dirs.add(abs_include)
|
||||||
|
|
||||||
abs_path = os.path.normpath(os.path.join(project_root, rel_path))
|
# Добавляем пути с заменой 'Src' на 'Inc', если путь заканчивается на 'Src'
|
||||||
|
additional_includes = set()
|
||||||
root, ext = os.path.splitext(abs_path)
|
for inc in include_dirs:
|
||||||
if ext.lower() == ".obj":
|
if inc.endswith(os.sep + "Src") or inc.endswith("/Src"):
|
||||||
c_path = root + ".c"
|
inc_inc = inc[:-3] + "Inc" # заменяем 'Src' на 'Inc'
|
||||||
else:
|
if os.path.isdir(inc_inc):
|
||||||
c_path = abs_path
|
additional_includes.add(inc_inc)
|
||||||
|
|
||||||
# Проверяем существование файла, если нет — пропускаем
|
|
||||||
if not os.path.isfile(c_path):
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Сохраняем только .c файлы
|
|
||||||
if c_path.lower().endswith(".c"):
|
|
||||||
c_files.append(c_path)
|
|
||||||
dir_path = os.path.dirname(c_path)
|
|
||||||
if dir_path and "DebugTools" not in dir_path:
|
|
||||||
include_dirs.add(dir_path)
|
|
||||||
|
|
||||||
|
include_dirs.update(additional_includes)
|
||||||
|
|
||||||
h_files = find_all_includes_recursive(c_files, include_dirs)
|
h_files = find_all_includes_recursive(c_files, include_dirs)
|
||||||
|
|
||||||
|
return sorted(c_files), sorted(h_files), sorted(include_dirs), sorted(defines)
|
||||||
|
|
||||||
return sorted(c_files), sorted(h_files), sorted(include_dirs)
|
|
||||||
|
def parse_project(project_file_path, project_root=None):
|
||||||
|
"""
|
||||||
|
Выбирает парсер в зависимости от расширения project_file_path:
|
||||||
|
- для *.uvprojx и *.uvproj вызывается парсер Keil
|
||||||
|
- для остальных - parse_makefile
|
||||||
|
|
||||||
|
project_root нужен для parse_makefile, если не передан - берется из project_file_path
|
||||||
|
"""
|
||||||
|
ext = os.path.splitext(project_file_path)[1].lower()
|
||||||
|
|
||||||
|
if ext in ['.uvprojx', '.uvproj']:
|
||||||
|
# Парсим Keil проект
|
||||||
|
return parse_uvprojx(project_file_path)
|
||||||
|
else:
|
||||||
|
# Парсим makefile
|
||||||
|
if project_root is None:
|
||||||
|
project_root = os.path.dirname(os.path.abspath(project_file_path))
|
||||||
|
return parse_makefile(project_file_path, project_root)
|
287
Src/path_hints.py
Normal file
287
Src/path_hints.py
Normal file
@ -0,0 +1,287 @@
|
|||||||
|
# path_hints.py
|
||||||
|
from __future__ import annotations
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import Dict, List, Optional, Tuple
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------- tokenization helpers ----------------------
|
||||||
|
|
||||||
|
def split_path_tokens(path: str) -> List[str]:
|
||||||
|
"""
|
||||||
|
Разбивает строку пути на логические части:
|
||||||
|
'foo[2].bar[1]->baz' -> ['foo', '[2]', 'bar', '[1]', 'baz']
|
||||||
|
Аналог твоей split_path(), но оставлена как чистая функция.
|
||||||
|
"""
|
||||||
|
tokens: List[str] = []
|
||||||
|
token = ''
|
||||||
|
i = 0
|
||||||
|
L = len(path)
|
||||||
|
while i < L:
|
||||||
|
c = path[i]
|
||||||
|
# '->'
|
||||||
|
if c == '-' and i + 1 < L and path[i:i+2] == '->':
|
||||||
|
if token:
|
||||||
|
tokens.append(token)
|
||||||
|
token = ''
|
||||||
|
i += 2
|
||||||
|
continue
|
||||||
|
# одиночный '-' в конце
|
||||||
|
if c == '-' and i == L - 1:
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
# '.'
|
||||||
|
if c == '.':
|
||||||
|
if token:
|
||||||
|
tokens.append(token)
|
||||||
|
token = ''
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
# '[' ... ']'
|
||||||
|
if c == '[':
|
||||||
|
if token:
|
||||||
|
tokens.append(token)
|
||||||
|
token = ''
|
||||||
|
idx = ''
|
||||||
|
while i < L and path[i] != ']':
|
||||||
|
idx += path[i]
|
||||||
|
i += 1
|
||||||
|
if i < L and path[i] == ']':
|
||||||
|
idx += ']'
|
||||||
|
i += 1
|
||||||
|
tokens.append(idx)
|
||||||
|
continue
|
||||||
|
# обычный символ
|
||||||
|
token += c
|
||||||
|
i += 1
|
||||||
|
if token:
|
||||||
|
tokens.append(token)
|
||||||
|
return tokens
|
||||||
|
|
||||||
|
|
||||||
|
def canonical_key(path: str) -> str:
|
||||||
|
"""
|
||||||
|
Преобразует путь к канонической форме для индекса / поиска:
|
||||||
|
- '->' -> '.'
|
||||||
|
- '[' -> '.['
|
||||||
|
- lower()
|
||||||
|
"""
|
||||||
|
p = path.replace('->', '.')
|
||||||
|
p = p.replace('[', '.[')
|
||||||
|
return p.lower()
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------- индекс узлов ----------------------
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PathNode:
|
||||||
|
"""
|
||||||
|
Узел в логическом дереве путей.
|
||||||
|
Храним:
|
||||||
|
- собственное имя (локальное, напр. 'controller' или '[3]')
|
||||||
|
- полный путь (оригинальный, как его должен видеть пользователь)
|
||||||
|
- тип (опционально; widget может хранить отдельно)
|
||||||
|
- дети
|
||||||
|
"""
|
||||||
|
name: str
|
||||||
|
full_path: str
|
||||||
|
type_str: str = ''
|
||||||
|
children: Dict[str, "PathNode"] = field(default_factory=dict)
|
||||||
|
|
||||||
|
def add_child(self, child: "PathNode") -> None:
|
||||||
|
self.children[child.name] = child
|
||||||
|
|
||||||
|
def get_children(self) -> List["PathNode"]:
|
||||||
|
"""
|
||||||
|
Вернуть список дочерних узлов, отсортированных по имени.
|
||||||
|
"""
|
||||||
|
return sorted(self.children.values(), key=lambda n: n.name)
|
||||||
|
|
||||||
|
class PathHints:
|
||||||
|
"""
|
||||||
|
Движок автоподсказок / completion.
|
||||||
|
Работает с плоским списком ПОЛНЫХ имён (как показываются пользователю).
|
||||||
|
Сам восстанавливает иерархию и выдаёт подсказки по текущему вводу.
|
||||||
|
Qt-независим.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self._paths: List[str] = []
|
||||||
|
self._types: Dict[str, str] = {} # full_path -> type_str (опционально)
|
||||||
|
self._index: Dict[str, PathNode] = {} # canonical full path -> node
|
||||||
|
self._root_children: Dict[str, PathNode] = {} # top-level по первому токену
|
||||||
|
|
||||||
|
# ------------ Подаём данные ------------
|
||||||
|
def set_paths(self,
|
||||||
|
paths: List[Tuple[str, Optional[str]]]
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
paths: список кортежей (full_path, type_str|None).
|
||||||
|
Пример: ('project.controller.read.errors.bit.status_er0', 'unsigned int')
|
||||||
|
Поля могут содержать '->' и индексы, т.е. строки в пользовательском формате.
|
||||||
|
|
||||||
|
NOTE: порядок не важен; дерево строится автоматически.
|
||||||
|
"""
|
||||||
|
self._paths = []
|
||||||
|
self._types.clear()
|
||||||
|
self._index.clear()
|
||||||
|
self._root_children.clear()
|
||||||
|
|
||||||
|
for p, t in paths:
|
||||||
|
if t is None:
|
||||||
|
t = ''
|
||||||
|
self._add_path(p, t)
|
||||||
|
|
||||||
|
def _add_path(self, full_path: str, type_str: str) -> None:
|
||||||
|
self._paths.append(full_path)
|
||||||
|
self._types[full_path] = type_str
|
||||||
|
|
||||||
|
toks = split_path_tokens(full_path)
|
||||||
|
if not toks:
|
||||||
|
return
|
||||||
|
|
||||||
|
cur_dict = self._root_children
|
||||||
|
cur_full = ''
|
||||||
|
parent_node: Optional[PathNode] = None
|
||||||
|
|
||||||
|
for i, tok in enumerate(toks):
|
||||||
|
# Собираем ПОЛНЫЙ путь
|
||||||
|
if cur_full == '':
|
||||||
|
cur_full = tok
|
||||||
|
else:
|
||||||
|
if tok.startswith('['):
|
||||||
|
cur_full += tok
|
||||||
|
else:
|
||||||
|
cur_full += '.' + tok
|
||||||
|
|
||||||
|
# Если узел уже есть
|
||||||
|
node = cur_dict.get(tok)
|
||||||
|
if node is None:
|
||||||
|
# --- ВАЖНО: full_path = cur_full ---
|
||||||
|
node = PathNode(name=tok, full_path=cur_full)
|
||||||
|
cur_dict[tok] = node
|
||||||
|
|
||||||
|
# Регистрируем все узлы, включая промежуточные
|
||||||
|
self._index[canonical_key(cur_full)] = node
|
||||||
|
|
||||||
|
parent_node = node
|
||||||
|
cur_dict = node.children
|
||||||
|
|
||||||
|
# В последний узел добавляем тип
|
||||||
|
if parent_node:
|
||||||
|
parent_node.type_str = type_str
|
||||||
|
|
||||||
|
|
||||||
|
# ------------ Поиск узла ------------
|
||||||
|
|
||||||
|
def find_node(self, path: str) -> Optional[PathNode]:
|
||||||
|
return self._index.get(canonical_key(path))
|
||||||
|
|
||||||
|
def get_children(self, full_path: str) -> List[PathNode]:
|
||||||
|
"""
|
||||||
|
Вернуть список дочерних узлов PathNode для заданного полного пути.
|
||||||
|
Если узел не найден — вернуть пустой список.
|
||||||
|
"""
|
||||||
|
node = self.find_node(full_path)
|
||||||
|
if node is None:
|
||||||
|
return []
|
||||||
|
return node.get_children()
|
||||||
|
# ------------ Подсказки ------------
|
||||||
|
|
||||||
|
def suggest(self,
|
||||||
|
text: str,
|
||||||
|
*,
|
||||||
|
include_partial: bool = True
|
||||||
|
) -> List[str]:
|
||||||
|
"""
|
||||||
|
Вернёт список *полных имён узлов*, подходящих под ввод.
|
||||||
|
Правила (упрощённо, повторяя твою update_completions()):
|
||||||
|
- Если текст пуст → top-level.
|
||||||
|
- Если заканчивается на '.' или '->' или '[' → вернуть детей текущего узла.
|
||||||
|
- Иначе → фильтр по последнему фрагменту (prefix substring match).
|
||||||
|
"""
|
||||||
|
text = text or ''
|
||||||
|
stripped = text.strip()
|
||||||
|
|
||||||
|
# пусто: top-level
|
||||||
|
if stripped == '':
|
||||||
|
return sorted(self._root_full_names())
|
||||||
|
|
||||||
|
# Завершение по разделителю?
|
||||||
|
if stripped.endswith('.') or stripped.endswith('->') or stripped.endswith('['):
|
||||||
|
base = stripped[:-1] if stripped.endswith('[') else stripped.rstrip('.').rstrip('>').rstrip('-')
|
||||||
|
node = self.find_node(base)
|
||||||
|
if node:
|
||||||
|
return self._children_full_names(node)
|
||||||
|
# не нашли базу — ничего
|
||||||
|
return []
|
||||||
|
|
||||||
|
# иначе: обычный поиск по последней части
|
||||||
|
toks = split_path_tokens(stripped)
|
||||||
|
prefix_last = toks[-1].lower() if toks else ''
|
||||||
|
parent_toks = toks[:-1]
|
||||||
|
|
||||||
|
if not parent_toks:
|
||||||
|
# фильтр top-level
|
||||||
|
res = []
|
||||||
|
for name, node in self._root_children.items():
|
||||||
|
if prefix_last == '' or prefix_last in name.lower():
|
||||||
|
res.append(node.full_path)
|
||||||
|
return sorted(res)
|
||||||
|
|
||||||
|
# есть родитель
|
||||||
|
parent_path = self._join_tokens(parent_toks)
|
||||||
|
parent_node = self.find_node(parent_path)
|
||||||
|
if not parent_node:
|
||||||
|
return []
|
||||||
|
res = []
|
||||||
|
for child in parent_node.children.values():
|
||||||
|
if prefix_last == '' or prefix_last in child.name.lower():
|
||||||
|
res.append(child.full_path)
|
||||||
|
return sorted(res)
|
||||||
|
|
||||||
|
def add_separator(self, full_path: str) -> str:
|
||||||
|
"""
|
||||||
|
Возвращает full_path с добавленным разделителем ('.' или '['),
|
||||||
|
если у узла есть дети и пользователь ещё не поставил разделитель.
|
||||||
|
Если первый ребёнок — массивный токен ('[0]') → добавляем '['.
|
||||||
|
Позже можно допилить '->' для указателей.
|
||||||
|
"""
|
||||||
|
node = self.find_node(full_path)
|
||||||
|
text = full_path
|
||||||
|
|
||||||
|
if node and node.children and not (
|
||||||
|
text.endswith('.') or text.endswith('->') or text.endswith('[')
|
||||||
|
):
|
||||||
|
first_child = next(iter(node.children.values()))
|
||||||
|
if first_child.name.startswith('['):
|
||||||
|
text += '[' # сразу начинаем индекс
|
||||||
|
else:
|
||||||
|
text += '.' # обычный переход
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
# ------------ внутренние вспомогательные ------------
|
||||||
|
|
||||||
|
def _root_full_names(self) -> List[str]:
|
||||||
|
return [node.full_path for node in self._root_children.values()]
|
||||||
|
|
||||||
|
def _children_full_names(self, node: PathNode) -> List[str]:
|
||||||
|
return [ch.full_path for ch in node.children.values()]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _join_tokens(tokens: List[str]) -> str:
|
||||||
|
"""
|
||||||
|
Собираем путь обратно. Для внутренних нужд (поиск), формат не критичен —
|
||||||
|
всё равно canonical_key() нормализует.
|
||||||
|
"""
|
||||||
|
if not tokens:
|
||||||
|
return ''
|
||||||
|
out = tokens[0]
|
||||||
|
for t in tokens[1:]:
|
||||||
|
if t.startswith('['):
|
||||||
|
out += t
|
||||||
|
else:
|
||||||
|
out += '.' + t
|
||||||
|
return out
|
@ -11,7 +11,7 @@ from clang import cindex
|
|||||||
from clang.cindex import Config
|
from clang.cindex import Config
|
||||||
import lxml.etree as ET
|
import lxml.etree as ET
|
||||||
from xml.dom import minidom
|
from xml.dom import minidom
|
||||||
from makefile_parser import parse_makefile
|
from makefile_parser import parse_project
|
||||||
from collections import deque
|
from collections import deque
|
||||||
import argparse
|
import argparse
|
||||||
import myXML
|
import myXML
|
||||||
@ -118,11 +118,11 @@ def get_canonical_typedef_file(var_type, include_dirs):
|
|||||||
break
|
break
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def analyze_variables_across_files(c_files, h_files, include_dirs):
|
def analyze_variables_across_files(c_files, h_files, include_dirs, global_defs):
|
||||||
optional_printf(PRINT_STATUS, "Starting analysis of variables across files...")
|
optional_printf(PRINT_STATUS, "Starting analysis of variables across files...")
|
||||||
index = clang.cindex.Index.create()
|
index = clang.cindex.Index.create()
|
||||||
args = [f"-I{inc}" for inc in include_dirs]
|
define_args = [f"-D{d}" for d in global_defs]
|
||||||
|
args = [f"-I{inc}" for inc in include_dirs] + define_args
|
||||||
unique_vars = {} # имя переменной → словарь с инфой
|
unique_vars = {} # имя переменной → словарь с инфой
|
||||||
h_files_needed = set()
|
h_files_needed = set()
|
||||||
vars_need_extern = {} # имя переменной → словарь без поля 'extern'
|
vars_need_extern = {} # имя переменной → словарь без поля 'extern'
|
||||||
@ -154,6 +154,7 @@ def analyze_variables_across_files(c_files, h_files, include_dirs):
|
|||||||
# Проверяем, начинается ли имя с "_" и содержит заглавные буквы или служебные символы
|
# Проверяем, начинается ли имя с "_" и содержит заглавные буквы или служебные символы
|
||||||
return bool(re.match(r"^_[_A-Z]", var_name))
|
return bool(re.match(r"^_[_A-Z]", var_name))
|
||||||
|
|
||||||
|
|
||||||
if node.kind == clang.cindex.CursorKind.VAR_DECL:
|
if node.kind == clang.cindex.CursorKind.VAR_DECL:
|
||||||
if node.semantic_parent.kind == clang.cindex.CursorKind.TRANSLATION_UNIT:
|
if node.semantic_parent.kind == clang.cindex.CursorKind.TRANSLATION_UNIT:
|
||||||
is_extern = (node.storage_class == clang.cindex.StorageClass.EXTERN)
|
is_extern = (node.storage_class == clang.cindex.StorageClass.EXTERN)
|
||||||
@ -170,7 +171,12 @@ def analyze_variables_across_files(c_files, h_files, include_dirs):
|
|||||||
return # игнорируем только явно известные служебные переменные
|
return # игнорируем только явно известные служебные переменные
|
||||||
if node.spelling == 'HUGE': # еще одна служеюная, которую хз как выделять
|
if node.spelling == 'HUGE': # еще одна служеюная, которую хз как выделять
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if 'Drivers' in node.location.file.name:
|
||||||
|
return
|
||||||
|
|
||||||
|
if 'uint' in node.spelling:
|
||||||
|
a = 1
|
||||||
# Проверяем, является ли тип указателем на функцию
|
# Проверяем, является ли тип указателем на функцию
|
||||||
# Признак: в типе есть '(' и ')' и '*', например: "void (*)(int)"
|
# Признак: в типе есть '(' и ')' и '*', например: "void (*)(int)"
|
||||||
if "(" in var_type and "*" in var_type and ")" in var_type:
|
if "(" in var_type and "*" in var_type and ")" in var_type:
|
||||||
@ -295,6 +301,8 @@ def strip_ptr_and_array(typename):
|
|||||||
|
|
||||||
return typename
|
return typename
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def analyze_typedefs_and_struct(typedefs, structs):
|
def analyze_typedefs_and_struct(typedefs, structs):
|
||||||
optional_printf(PRINT_STATUS, "Resolving typedefs and expanding struct field types...")
|
optional_printf(PRINT_STATUS, "Resolving typedefs and expanding struct field types...")
|
||||||
|
|
||||||
@ -422,10 +430,28 @@ def contains_anywhere_in_node(node, target: str) -> bool:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def analyze_typedefs_and_structs_across_files(c_files, include_dirs):
|
|
||||||
|
def try_guess_std_include():
|
||||||
|
# Популярные места, где может лежать stdint.h
|
||||||
|
guesses = [
|
||||||
|
r"C:\Keil_v5\ARM\ARMCLANG\include",
|
||||||
|
r"C:\Program Files (x86)\GNU Arm Embedded Toolchain",
|
||||||
|
r"C:\Program Files (x86)\Arm GNU Toolchain"
|
||||||
|
]
|
||||||
|
|
||||||
|
found = []
|
||||||
|
for base in guesses:
|
||||||
|
for root, dirs, files in os.walk(base):
|
||||||
|
if "stdint.h" in files:
|
||||||
|
found.append(root)
|
||||||
|
return found
|
||||||
|
|
||||||
|
def analyze_typedefs_and_structs_across_files(c_files, include_dirs, global_defs):
|
||||||
optional_printf(PRINT_STATUS, "Starting analysis of typedefs and structs across files...")
|
optional_printf(PRINT_STATUS, "Starting analysis of typedefs and structs across files...")
|
||||||
index = clang.cindex.Index.create()
|
index = clang.cindex.Index.create()
|
||||||
args = [f"-I{inc}" for inc in include_dirs]
|
define_args = [f"-D{d}" for d in global_defs]
|
||||||
|
extra_std_include_dirs = try_guess_std_include()
|
||||||
|
args = [f"-I{inc}" for inc in include_dirs] + extra_std_include_dirs + define_args
|
||||||
|
|
||||||
unique_typedefs_raw = {}
|
unique_typedefs_raw = {}
|
||||||
unique_structs_raw = {}
|
unique_structs_raw = {}
|
||||||
@ -452,7 +478,6 @@ def analyze_typedefs_and_structs_across_files(c_files, include_dirs):
|
|||||||
|
|
||||||
raw_name = node.spelling
|
raw_name = node.spelling
|
||||||
normalized_name = normalize_type_name(raw_name)
|
normalized_name = normalize_type_name(raw_name)
|
||||||
|
|
||||||
# struct_name всегда с префиксом
|
# struct_name всегда с префиксом
|
||||||
if node.spelling and "unnamed" not in normalized_name:
|
if node.spelling and "unnamed" not in normalized_name:
|
||||||
struct_name = f"{prefix}{normalized_name}"
|
struct_name = f"{prefix}{normalized_name}"
|
||||||
@ -855,10 +880,10 @@ Usage example:
|
|||||||
print(f"Error: Makefile path '{makefile_path}' does not exist.")
|
print(f"Error: Makefile path '{makefile_path}' does not exist.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
c_files, h_files, include_dirs = parse_makefile(makefile_path, proj_path)
|
c_files, h_files, include_dirs, global_defs = parse_project(makefile_path, proj_path)
|
||||||
|
|
||||||
vars, includes, externs = analyze_variables_across_files(c_files, h_files, include_dirs)
|
vars, includes, externs = analyze_variables_across_files(c_files, h_files, include_dirs, global_defs)
|
||||||
typedefs, structs = analyze_typedefs_and_structs_across_files(c_files, include_dirs)
|
typedefs, structs = analyze_typedefs_and_structs_across_files(c_files, include_dirs, global_defs)
|
||||||
|
|
||||||
vars = dict(sorted(vars.items()))
|
vars = dict(sorted(vars.items()))
|
||||||
includes = get_sorted_headers(c_files, includes, include_dirs)
|
includes = get_sorted_headers(c_files, includes, include_dirs)
|
||||||
@ -898,10 +923,10 @@ def run_scan(proj_path, makefile_path, output_xml, verbose=2):
|
|||||||
if not os.path.isfile(makefile_path):
|
if not os.path.isfile(makefile_path):
|
||||||
raise FileNotFoundError(f"Makefile path '{makefile_path}' does not exist.")
|
raise FileNotFoundError(f"Makefile path '{makefile_path}' does not exist.")
|
||||||
|
|
||||||
c_files, h_files, include_dirs = parse_makefile(makefile_path, proj_path)
|
c_files, h_files, include_dirs, global_defs = parse_project(makefile_path, proj_path)
|
||||||
|
|
||||||
vars, includes, externs = analyze_variables_across_files(c_files, h_files, include_dirs)
|
vars, includes, externs = analyze_variables_across_files(c_files, h_files, include_dirs, global_defs)
|
||||||
typedefs, structs = analyze_typedefs_and_structs_across_files(c_files, include_dirs)
|
typedefs, structs = analyze_typedefs_and_structs_across_files(c_files, include_dirs, global_defs)
|
||||||
|
|
||||||
vars = dict(sorted(vars.items()))
|
vars = dict(sorted(vars.items()))
|
||||||
includes = get_sorted_headers(c_files, includes, include_dirs)
|
includes = get_sorted_headers(c_files, includes, include_dirs)
|
||||||
|
504
Src/tms_debugvar_lowlevel.py
Normal file
504
Src/tms_debugvar_lowlevel.py
Normal file
@ -0,0 +1,504 @@
|
|||||||
|
"""
|
||||||
|
LowLevelSelectorWidget (refactored)
|
||||||
|
-----------------------------------
|
||||||
|
Версия, использующая VariableTableWidget вместо самодельной таблицы selected_vars_table.
|
||||||
|
|
||||||
|
Ключевые изменения:
|
||||||
|
* Вместо QTableWidget с 6 колонками теперь встраивается VariableTableWidget (8 колонок: №, En, Name, Origin Type, Base Type, IQ Type, Return Type, Short Name).
|
||||||
|
* Логика sync <-> self._all_available_vars перенесена в _on_var_table_changed() и _pull_from_var_table().
|
||||||
|
* Поддержка политики хранения типов:
|
||||||
|
- ptr_type: строковое имя (без префикса `pt_`).
|
||||||
|
- ptr_type_enum: числовой индекс (см. PT_ENUM_ORDER).
|
||||||
|
- Для совместимости с VariableTableWidget: поле `pt_type` = 'pt_<name>'.
|
||||||
|
- IQ / Return: аналогично (`iq_type` / `iq_type_enum`, `return_type` / `return_type_enum`).
|
||||||
|
* Функции получения выбранных переменных теперь читают данные из VariableTableWidget.
|
||||||
|
* Убраны неиспользуемые методы, связанные с прежней таблицей (комбо‑боксы и т.п.).
|
||||||
|
|
||||||
|
Как интегрировать:
|
||||||
|
1. Поместите этот файл рядом с module VariableTableWidget (см. импорт ниже). Если класс VariableTableWidget находится в том же файле — удалите строку импорта и используйте напрямую.
|
||||||
|
2. Убедитесь, что VariablesXML предоставляет методы get_all_vars_data() (list[dict]) и, при наличии, get_struct_map() -> dict[type_name -> dict[field_name -> field_type]]. Если такого метода нет, передаём пустой {} и автодополнение по структурам будет недоступно.
|
||||||
|
3. Отметьте переменные в VariableSelectorDialog (как и раньше) — он обновит self._all_available_vars. После закрытия диалога вызывается self._populate_var_table().
|
||||||
|
4. Для чтения выбранных переменных используйте get_selected_variables_and_addresses(); она вернёт список словарей в унифицированном формате.
|
||||||
|
|
||||||
|
Примечание о совместимости: VariableTableWidget работает с ключами `pt_type`, `iq_type`, `return_type` (строки с префиксами). Мы поддерживаем дублирование этих полей с «новыми» полями без префикса и enum‑значениями.
|
||||||
|
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
import datetime
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import List, Dict, Optional, Tuple, Any
|
||||||
|
|
||||||
|
from PySide2 import QtCore, QtGui
|
||||||
|
from PySide2.QtWidgets import (
|
||||||
|
QWidget, QVBoxLayout, QPushButton, QLabel, QHBoxLayout, QFileDialog, QMessageBox,
|
||||||
|
QMainWindow, QApplication, QSizePolicy, QSpinBox, QGroupBox, QSplitter, QFormLayout
|
||||||
|
)
|
||||||
|
|
||||||
|
# Локальные импорты
|
||||||
|
from path_hints import PathHints
|
||||||
|
from generate_debug_vars import choose_type_map, type_map
|
||||||
|
from var_selector_window import VariableSelectorDialog
|
||||||
|
from allvars_xml_parser import VariablesXML
|
||||||
|
|
||||||
|
# Импортируем готовую таблицу
|
||||||
|
# ЗАМЕТКА: замените на реальное имя файла/модуля, если отличается.
|
||||||
|
from var_table import VariableTableWidget, rows as VT_ROWS # noqa: F401
|
||||||
|
|
||||||
|
# ------------------------------------------------------------ Enumerations --
|
||||||
|
# Порядок фиксируем на основании предыдущей версии. При необходимости расширьте.
|
||||||
|
PT_ENUM_ORDER = [
|
||||||
|
'unknown','int8','int16','int32','int64',
|
||||||
|
'uint8','uint16','uint32','uint64','float',
|
||||||
|
'struct','union'
|
||||||
|
]
|
||||||
|
|
||||||
|
IQ_ENUM_ORDER = [
|
||||||
|
'iq_none','iq','iq1','iq2','iq3','iq4','iq5','iq6',
|
||||||
|
'iq7','iq8','iq9','iq10','iq11','iq12','iq13','iq14',
|
||||||
|
'iq15','iq16','iq17','iq18','iq19','iq20','iq21','iq22',
|
||||||
|
'iq23','iq24','iq25','iq26','iq27','iq28','iq29','iq30'
|
||||||
|
]
|
||||||
|
|
||||||
|
PT_ENUM_VALUE: Dict[str, int] = {name: idx for idx, name in enumerate(PT_ENUM_ORDER)}
|
||||||
|
IQ_ENUM_VALUE: Dict[str, int] = {name: idx for idx, name in enumerate(IQ_ENUM_ORDER)}
|
||||||
|
PT_ENUM_NAME_FROM_VAL: Dict[int, str] = {v: k for k, v in PT_ENUM_VALUE.items()}
|
||||||
|
IQ_ENUM_NAME_FROM_VAL: Dict[int, str] = {v: k for k, v in IQ_ENUM_VALUE.items()}
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------- Address / validation helpers --
|
||||||
|
HEX_ADDR_MASK = QtCore.QRegExp(r"0x[0-9A-Fa-f]{0,6}")
|
||||||
|
class HexAddrValidator(QtGui.QRegExpValidator):
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(HEX_ADDR_MASK, parent)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def normalize(text: str) -> str:
|
||||||
|
if not text:
|
||||||
|
return '0x000000'
|
||||||
|
try:
|
||||||
|
val = int(text,16)
|
||||||
|
except ValueError:
|
||||||
|
return '0x000000'
|
||||||
|
return f"0x{val & 0xFFFFFF:06X}"
|
||||||
|
|
||||||
|
|
||||||
|
class LowLevelSelectorWidget(QWidget):
|
||||||
|
variablePrepared = QtCore.Signal(dict)
|
||||||
|
xmlLoaded = QtCore.Signal(str)
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.setWindowTitle('LowLevel Variable Selector')
|
||||||
|
self._xml: Optional[VariablesXML] = None
|
||||||
|
self._paths: List[str] = []
|
||||||
|
self._path_info: Dict[str, Tuple[int, str]] = {}
|
||||||
|
self._addr_index: Dict[int, Optional[str]] = {}
|
||||||
|
self._hints = PathHints()
|
||||||
|
self._all_available_vars: List[Dict[str, Any]] = []
|
||||||
|
self.dt = None
|
||||||
|
self.flat_vars = None
|
||||||
|
|
||||||
|
# --- NEW ---
|
||||||
|
self.btn_read_once = QPushButton("Read Once")
|
||||||
|
self.btn_start_polling = QPushButton("Start Polling")
|
||||||
|
self.spin_interval = QSpinBox()
|
||||||
|
self.spin_interval.setRange(50, 10000)
|
||||||
|
self.spin_interval.setValue(500)
|
||||||
|
self.spin_interval.setSuffix(" ms")
|
||||||
|
|
||||||
|
self._build_ui()
|
||||||
|
self._connect()
|
||||||
|
|
||||||
|
def _build_ui(self):
|
||||||
|
tab = QWidget()
|
||||||
|
main_layout = QVBoxLayout(tab)
|
||||||
|
|
||||||
|
# --- Variable Selector ---
|
||||||
|
g_selector = QGroupBox("Variable Selector")
|
||||||
|
selector_layout = QVBoxLayout(g_selector)
|
||||||
|
|
||||||
|
form_selector = QFormLayout()
|
||||||
|
|
||||||
|
# --- XML File chooser ---
|
||||||
|
file_layout = QHBoxLayout()
|
||||||
|
self.btn_load = QPushButton('Load XML...')
|
||||||
|
self.lbl_file = QLabel('<no file>')
|
||||||
|
self.lbl_file.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse)
|
||||||
|
file_layout.addWidget(self.btn_load)
|
||||||
|
file_layout.addWidget(self.lbl_file, 1)
|
||||||
|
form_selector.addRow("XML File:", file_layout)
|
||||||
|
|
||||||
|
# --- Interval SpinBox ---
|
||||||
|
self.spin_interval = QSpinBox()
|
||||||
|
self.spin_interval.setRange(50, 10000)
|
||||||
|
self.spin_interval.setValue(500)
|
||||||
|
self.spin_interval.setSuffix(" ms")
|
||||||
|
form_selector.addRow("Interval:", self.spin_interval)
|
||||||
|
|
||||||
|
selector_layout.addLayout(form_selector)
|
||||||
|
|
||||||
|
# --- Buttons ---
|
||||||
|
self.btn_read_once = QPushButton("Read Once")
|
||||||
|
self.btn_start_polling = QPushButton("Start Polling")
|
||||||
|
btn_layout = QHBoxLayout()
|
||||||
|
btn_layout.addWidget(self.btn_read_once)
|
||||||
|
btn_layout.addWidget(self.btn_start_polling)
|
||||||
|
selector_layout.addLayout(btn_layout)
|
||||||
|
|
||||||
|
# --- Table ---
|
||||||
|
g_table = QGroupBox("Table")
|
||||||
|
table_layout = QVBoxLayout(g_table)
|
||||||
|
self.btn_open_var_selector = QPushButton("Выбрать переменные...")
|
||||||
|
table_layout.addWidget(self.btn_open_var_selector)
|
||||||
|
self.var_table = VariableTableWidget(self, show_value_instead_of_shortname=1)
|
||||||
|
self.var_table.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||||
|
table_layout.addWidget(self.var_table)
|
||||||
|
|
||||||
|
# --- Timestamp (moved here) ---
|
||||||
|
self.lbl_timestamp = QLabel('Timestamp: -')
|
||||||
|
table_layout.addWidget(self.lbl_timestamp)
|
||||||
|
|
||||||
|
# --- Splitter (Selector + Table) ---
|
||||||
|
v_split = QSplitter(QtCore.Qt.Vertical)
|
||||||
|
v_split.addWidget(g_selector)
|
||||||
|
v_split.addWidget(g_table)
|
||||||
|
v_split.setStretchFactor(0, 1)
|
||||||
|
v_split.setStretchFactor(1, 3)
|
||||||
|
|
||||||
|
main_layout.addWidget(v_split)
|
||||||
|
self.setLayout(main_layout)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def _connect(self):
|
||||||
|
self.btn_load.clicked.connect(self._on_load_xml)
|
||||||
|
self.btn_open_var_selector.clicked.connect(self._on_open_variable_selector)
|
||||||
|
|
||||||
|
# ------------------------------------------------------ XML loading ----
|
||||||
|
def _on_load_xml(self):
|
||||||
|
path, _ = QFileDialog.getOpenFileName(
|
||||||
|
self, 'Select variables XML', '', 'XML Files (*.xml);;All Files (*)')
|
||||||
|
if not path:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
self._xml = VariablesXML(path)
|
||||||
|
self.flat_vars = {v['name']: v for v in self._xml.flattened()}
|
||||||
|
# Получаем сырые данные по переменным
|
||||||
|
self._all_available_vars = self._xml.get_all_vars_data()
|
||||||
|
except Exception as e:
|
||||||
|
QMessageBox.critical(self, 'Parse error', f'Ошибка парсинга:\n{e}')
|
||||||
|
return
|
||||||
|
|
||||||
|
self.lbl_file.setText(path)
|
||||||
|
self.lbl_timestamp.setText(f'Timestamp: {self._xml.timestamp or "-"}')
|
||||||
|
|
||||||
|
self._populate_internal_maps_from_all_vars()
|
||||||
|
self._apply_timestamp_to_date()
|
||||||
|
self.xmlLoaded.emit(path)
|
||||||
|
self._log(f'Loaded {path}, variables={len(self._all_available_vars)})')
|
||||||
|
|
||||||
|
|
||||||
|
def _apply_timestamp_to_date(self):
|
||||||
|
if not (self._xml and self._xml.timestamp):
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
# Пример: "Sat Jul 19 15:27:59 2025"
|
||||||
|
self.dt = datetime.datetime.strptime(self._xml.timestamp, "%a %b %d %H:%M:%S %Y")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Ошибка разбора timestamp '{self._xml.timestamp}': {e}")
|
||||||
|
|
||||||
|
# ------------------------------------------ Variable selector dialog ----
|
||||||
|
def _on_open_variable_selector(self):
|
||||||
|
if not self._xml:
|
||||||
|
QMessageBox.warning(self, 'No XML', 'Сначала загрузите XML файл.')
|
||||||
|
return
|
||||||
|
|
||||||
|
dialog = VariableSelectorDialog(
|
||||||
|
table=None, # не используем встроенную таблицу
|
||||||
|
all_vars=self._all_available_vars,
|
||||||
|
structs=None, # при необходимости подайте реальные структуры из XML
|
||||||
|
typedefs=None, # ...
|
||||||
|
xml_path=None, # по запросу пользователя xml_path = None
|
||||||
|
parent=self
|
||||||
|
)
|
||||||
|
if dialog.exec_() == dialog.Accepted:
|
||||||
|
# Диалог обновил self._all_available_vars напрямую
|
||||||
|
self._populate_internal_maps_from_all_vars()
|
||||||
|
self._populate_var_table()
|
||||||
|
self._log("Variable selection updated.")
|
||||||
|
|
||||||
|
# ----------------------------------------------------- Populate table ----
|
||||||
|
def _populate_var_table(self):
|
||||||
|
"""Отобразить переменные (show_var == 'true') в VariableTableWidget."""
|
||||||
|
if not self._all_available_vars:
|
||||||
|
self.var_table.setRowCount(0)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Нормализуем все записи перед передачей таблице.
|
||||||
|
for var in self._all_available_vars:
|
||||||
|
self._normalize_var_record(var)
|
||||||
|
|
||||||
|
# Карта структур для автодополнения (если VariablesXML предоставляет)
|
||||||
|
try:
|
||||||
|
structs_map = self._xml.get_struct_map() if self._xml else {}
|
||||||
|
except AttributeError:
|
||||||
|
structs_map = {}
|
||||||
|
|
||||||
|
# populate() принимает: (vars_list, structs, on_change_callback)
|
||||||
|
self.var_table.populate(self._all_available_vars, structs_map, self._on_var_table_changed)
|
||||||
|
|
||||||
|
# -------------------------------------------------- Table change slot ----
|
||||||
|
def _on_var_table_changed(self, *args, **kwargs): # noqa: D401 (неиспользуемые)
|
||||||
|
"""Вызывается при любом изменении в VariableTableWidget.
|
||||||
|
|
||||||
|
Читаем данные из таблицы, мержим в self._all_available_vars (по имени),
|
||||||
|
пересобираем служебные индексы.
|
||||||
|
"""
|
||||||
|
updated = self.var_table.read_data() # list[dict]
|
||||||
|
|
||||||
|
# создаём индекс по имени из master списка
|
||||||
|
idx_by_name = {v.get('name'): v for v in self._all_available_vars if v.get('name')}
|
||||||
|
|
||||||
|
for rec in updated:
|
||||||
|
nm = rec.get('name')
|
||||||
|
if not nm:
|
||||||
|
continue
|
||||||
|
dst = idx_by_name.get(nm)
|
||||||
|
if not dst:
|
||||||
|
# Новая запись; добавляем базовые поля
|
||||||
|
dst = {
|
||||||
|
'name': nm,
|
||||||
|
'address': 0,
|
||||||
|
'file': '', 'extern': 'false', 'static': 'false',
|
||||||
|
}
|
||||||
|
self._all_available_vars.append(dst)
|
||||||
|
idx_by_name[nm] = dst
|
||||||
|
|
||||||
|
# перенести видимые поля
|
||||||
|
dst['show_var'] = str(bool(rec.get('show_var'))).lower()
|
||||||
|
dst['enable'] = str(bool(rec.get('enable'))).lower()
|
||||||
|
dst['shortname']= rec.get('shortname', nm)
|
||||||
|
dst['type'] = rec.get('type', dst.get('type',''))
|
||||||
|
|
||||||
|
# типы (строковые, с префиксами) -> нормализуем
|
||||||
|
pt_pref = rec.get('pt_type','pt_unknown') # 'pt_int16'
|
||||||
|
iq_pref = rec.get('iq_type','t_iq_none') # 't_iq10' etc.
|
||||||
|
rt_pref = rec.get('return_type', iq_pref)
|
||||||
|
|
||||||
|
self._assign_types_from_prefixed(dst, pt_pref, iq_pref, rt_pref)
|
||||||
|
|
||||||
|
# Пересобрать карты путей/адресов
|
||||||
|
self._populate_internal_maps_from_all_vars()
|
||||||
|
|
||||||
|
# --------------------------------- Normalize var record (public-ish) ----
|
||||||
|
def _normalize_var_record(self, var: Dict[str, Any]):
|
||||||
|
"""Унифицирует записи переменной.
|
||||||
|
|
||||||
|
Требуемые поля после нормализации:
|
||||||
|
var['ptr_type'] -> str (напр. 'int16')
|
||||||
|
var['ptr_type_enum'] -> int
|
||||||
|
var['iq_type'] -> str ('iq10')
|
||||||
|
var['iq_type_enum'] -> int
|
||||||
|
var['return_type'] -> str ('iq10')
|
||||||
|
var['return_type_enum']-> int
|
||||||
|
var['pt_type'] -> 'pt_<ptr_type>' (для совместимости с VariableTableWidget)
|
||||||
|
var['return_type_pref']-> 't_<return_type>' (см. ниже) # не обяз.
|
||||||
|
|
||||||
|
Дополнительно корректируем show_var/enable и адрес.
|
||||||
|
"""
|
||||||
|
# --- show_var / enable
|
||||||
|
var['show_var'] = str(var.get('show_var', 'false')).lower()
|
||||||
|
var['enable'] = str(var.get('enable', 'true')).lower()
|
||||||
|
|
||||||
|
# --- address
|
||||||
|
if not var.get('address'):
|
||||||
|
var_name = var.get('name')
|
||||||
|
# Ищем в self.flat_vars
|
||||||
|
if hasattr(self, 'flat_vars') and isinstance(self.flat_vars, dict):
|
||||||
|
flat_entry = self.flat_vars.get(var_name)
|
||||||
|
if flat_entry and 'address' in flat_entry:
|
||||||
|
var['address'] = flat_entry['address']
|
||||||
|
else:
|
||||||
|
var['address'] = 0
|
||||||
|
else:
|
||||||
|
var['address'] = 0
|
||||||
|
else:
|
||||||
|
# Нормализация адреса (если строка типа '0x1234')
|
||||||
|
try:
|
||||||
|
if isinstance(var['address'], str):
|
||||||
|
var['address'] = int(var['address'], 16)
|
||||||
|
except ValueError:
|
||||||
|
var['address'] = 0
|
||||||
|
|
||||||
|
|
||||||
|
# --- ptr_type (строка)
|
||||||
|
name = None
|
||||||
|
if isinstance(var.get('ptr_type'), str):
|
||||||
|
name = var['ptr_type']
|
||||||
|
elif isinstance(var.get('ptr_type_name'), str):
|
||||||
|
name = var['ptr_type_name']
|
||||||
|
elif isinstance(var.get('pt_type'), str):
|
||||||
|
name = var['pt_type'].replace('pt_','')
|
||||||
|
elif isinstance(var.get('ptr_type'), int):
|
||||||
|
name = PT_ENUM_NAME_FROM_VAL.get(var['ptr_type'], 'unknown')
|
||||||
|
else:
|
||||||
|
name = self._map_type_to_ptr_enum(var.get('type'))
|
||||||
|
val = PT_ENUM_VALUE.get(name, 0)
|
||||||
|
var['ptr_type'] = name
|
||||||
|
var['ptr_type_enum'] = val
|
||||||
|
var['pt_type'] = f'pt_{name}'
|
||||||
|
|
||||||
|
# ---------------------------------------------- prefixed assign helper ----
|
||||||
|
def _assign_types_from_prefixed(self, dst: Dict[str, Any], pt_pref: str, iq_pref: str, rt_pref: str):
|
||||||
|
"""Парсит строки вида 'pt_int16', 't_iq10' и записывает нормализованные поля."""
|
||||||
|
pt_name = pt_pref.replace('pt_','') if pt_pref else 'unknown'
|
||||||
|
iq_name = iq_pref
|
||||||
|
if iq_name.startswith('t_'):
|
||||||
|
iq_name = iq_name[2:]
|
||||||
|
rt_name = rt_pref
|
||||||
|
if rt_name.startswith('t_'):
|
||||||
|
rt_name = rt_name[2:]
|
||||||
|
|
||||||
|
dst['ptr_type'] = pt_name
|
||||||
|
dst['ptr_type_enum'] = PT_ENUM_VALUE.get(pt_name, 0)
|
||||||
|
dst['pt_type'] = f'pt_{pt_name}'
|
||||||
|
|
||||||
|
dst['iq_type'] = iq_name
|
||||||
|
dst['iq_type_enum'] = IQ_ENUM_VALUE.get(iq_name, 0)
|
||||||
|
|
||||||
|
dst['return_type'] = rt_name
|
||||||
|
dst['return_type_enum'] = IQ_ENUM_VALUE.get(rt_name, dst['iq_type_enum'])
|
||||||
|
dst['return_type_pref'] = f't_{rt_name}'
|
||||||
|
|
||||||
|
# ------------------------------------------ Populate internal maps ----
|
||||||
|
def _populate_internal_maps_from_all_vars(self):
|
||||||
|
self._path_info.clear()
|
||||||
|
self._addr_index.clear()
|
||||||
|
self._paths.clear()
|
||||||
|
|
||||||
|
for var in self._all_available_vars:
|
||||||
|
nm = var.get('name')
|
||||||
|
tp = var.get('type')
|
||||||
|
addr = var.get('address')
|
||||||
|
if nm is None:
|
||||||
|
continue
|
||||||
|
if addr is None:
|
||||||
|
addr = 0
|
||||||
|
var['address'] = 0
|
||||||
|
self._paths.append(nm)
|
||||||
|
self._path_info[nm] = (addr, tp)
|
||||||
|
if addr in self._addr_index:
|
||||||
|
self._addr_index[addr] = None
|
||||||
|
else:
|
||||||
|
self._addr_index[addr] = nm
|
||||||
|
|
||||||
|
# Обновим подсказки
|
||||||
|
self._hints.set_paths([(p, self._path_info[p][1]) for p in self._paths])
|
||||||
|
|
||||||
|
# -------------------------------------------------- Public helpers ----
|
||||||
|
def get_selected_variables_and_addresses(self) -> List[Dict[str, Any]]:
|
||||||
|
"""Возвращает список выбранных переменных (show_var == true) с адресами и типами.
|
||||||
|
|
||||||
|
Чтение из VariableTableWidget + подстановка адресов/прочих служебных полей
|
||||||
|
из master списка.
|
||||||
|
"""
|
||||||
|
tbl_data = self.var_table.read_data() # список dict'ов в формате VariableTableWidget
|
||||||
|
idx_by_name = {v.get('name'): v for v in self._all_available_vars if v.get('name')}
|
||||||
|
|
||||||
|
out: List[Dict[str, Any]] = []
|
||||||
|
for rec in tbl_data:
|
||||||
|
nm = rec.get('name')
|
||||||
|
if not nm:
|
||||||
|
continue
|
||||||
|
src = idx_by_name.get(nm, {})
|
||||||
|
addr = src.get('address')
|
||||||
|
if addr is None or addr == '' or addr == 0:
|
||||||
|
src['address'] = self.flat_vars.get(nm, {}).get('address', 0)
|
||||||
|
else:
|
||||||
|
# если это строка "0x..." — конвертируем в int
|
||||||
|
if isinstance(addr, str) and addr.startswith('0x'):
|
||||||
|
try:
|
||||||
|
src['address'] = int(addr, 16)
|
||||||
|
except ValueError:
|
||||||
|
src['address'] = self.flat_vars.get(nm, {}).get('address', 0)
|
||||||
|
type_str = src.get('type', rec.get('type','N/A'))
|
||||||
|
|
||||||
|
# нормализация типов
|
||||||
|
tmp = dict(src) # copy src to preserve extra fields (file, extern, ...)
|
||||||
|
self._assign_types_from_prefixed(tmp,
|
||||||
|
rec.get('pt_type','pt_unknown'),
|
||||||
|
rec.get('iq_type','t_iq_none'),
|
||||||
|
rec.get('return_type', rec.get('iq_type','t_iq_none')))
|
||||||
|
tmp['show_var'] = str(bool(rec.get('show_var'))).lower()
|
||||||
|
tmp['enable'] = str(bool(rec.get('enable'))).lower()
|
||||||
|
tmp['name'] = nm
|
||||||
|
tmp['address'] = addr
|
||||||
|
tmp['type'] = type_str
|
||||||
|
out.append(tmp)
|
||||||
|
return out
|
||||||
|
|
||||||
|
def get_datetime(self):
|
||||||
|
return self.dt
|
||||||
|
|
||||||
|
def set_variable_value(self, var_name: str, value: Any):
|
||||||
|
# 1. Обновляем master-список переменных
|
||||||
|
found = None
|
||||||
|
for var in self._all_available_vars:
|
||||||
|
if var.get('name') == var_name:
|
||||||
|
var['value'] = value
|
||||||
|
found = var
|
||||||
|
break
|
||||||
|
|
||||||
|
if not found:
|
||||||
|
# Если переменной нет в списке, можно либо проигнорировать, либо добавить.
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 2. Обновляем отображение в таблице
|
||||||
|
self.var_table.populate(self._all_available_vars, {}, self._on_var_table_changed)
|
||||||
|
return True
|
||||||
|
|
||||||
|
# --------------- Address mapping / type mapping helpers ---------------
|
||||||
|
|
||||||
|
|
||||||
|
def _map_type_to_ptr_enum(self, type_str: Optional[str]) -> str:
|
||||||
|
if not type_str:
|
||||||
|
return 'unknown'
|
||||||
|
low = type_str.lower()
|
||||||
|
token = low.replace('*',' ').replace('[',' ')
|
||||||
|
return type_map.get(token, 'unknown').replace('pt_','')
|
||||||
|
|
||||||
|
# ----------------------------------------------------------- Logging --
|
||||||
|
def _log(self, msg: str):
|
||||||
|
print(f"[LowLevelSelectorWidget Log] {msg}")
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Тест‑прогоночка (ручной) --------------------------------------------------
|
||||||
|
# Запускать только вручную: python LowLevelSelectorWidget_refactored.py <xml>
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# ----------------------------------------------------------- Demo window --
|
||||||
|
class _DemoWindow(QMainWindow):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.setWindowTitle('LowLevel Selector Demo')
|
||||||
|
self.selector = LowLevelSelectorWidget(self)
|
||||||
|
self.setCentralWidget(self.selector)
|
||||||
|
self.selector.variablePrepared.connect(self.on_var)
|
||||||
|
|
||||||
|
def on_var(self, data: dict):
|
||||||
|
print('Variable prepared ->', data)
|
||||||
|
|
||||||
|
def closeEvent(self, ev):
|
||||||
|
self.setCentralWidget(None)
|
||||||
|
super().closeEvent(ev)
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------- main ---
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app = QApplication(sys.argv)
|
||||||
|
w = _DemoWindow()
|
||||||
|
w.resize(640, 520)
|
||||||
|
w.show()
|
||||||
|
sys.exit(app.exec_())
|
1254
Src/tms_debugvar_term.py
Normal file
1254
Src/tms_debugvar_term.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,86 +1,64 @@
|
|||||||
import re
|
# variable_select_widget.py
|
||||||
|
import pickle
|
||||||
|
import hashlib
|
||||||
|
from typing import List, Dict, Any, Optional
|
||||||
|
|
||||||
from PySide2.QtWidgets import (
|
from PySide2.QtWidgets import (
|
||||||
QWidget, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QLineEdit,
|
QWidget, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QLineEdit,
|
||||||
QHeaderView, QCompleter
|
QHeaderView, QCompleter
|
||||||
)
|
)
|
||||||
from PySide2.QtGui import QKeyEvent
|
from PySide2.QtGui import QKeyEvent
|
||||||
from PySide2.QtCore import Qt, QStringListModel
|
from PySide2.QtCore import Qt, QStringListModel
|
||||||
import pickle
|
|
||||||
import time
|
|
||||||
import hashlib
|
|
||||||
|
|
||||||
|
from path_hints import PathHints, canonical_key, split_path_tokens
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# utils
|
||||||
|
# ------------------------------------------------------------------
|
||||||
def compute_vars_hash(vars_list):
|
def compute_vars_hash(vars_list):
|
||||||
return hashlib.sha1(pickle.dumps(vars_list)).hexdigest()
|
return hashlib.sha1(pickle.dumps(vars_list)).hexdigest()
|
||||||
|
|
||||||
# Вспомогательные функции, которые теперь будут использоваться виджетом
|
|
||||||
def split_path(path):
|
|
||||||
"""
|
|
||||||
Разбивает путь на компоненты:
|
|
||||||
- 'foo[2].bar[1]->baz' → ['foo', '[2]', 'bar', '[1]', 'baz']
|
|
||||||
Если видит '-' в конце строки (без '>' после) — обрезает этот '-'
|
|
||||||
"""
|
|
||||||
tokens = []
|
|
||||||
token = ''
|
|
||||||
i = 0
|
|
||||||
length = len(path)
|
|
||||||
while i < length:
|
|
||||||
c = path[i]
|
|
||||||
# Разделители: '->' и '.'
|
|
||||||
if c == '-' and i + 1 < length and path[i:i+2] == '->':
|
|
||||||
if token:
|
|
||||||
tokens.append(token)
|
|
||||||
token = ''
|
|
||||||
i += 2
|
|
||||||
continue
|
|
||||||
elif c == '-' and i == length - 1:
|
|
||||||
# '-' на конце строки без '>' после — просто пропускаем его
|
|
||||||
i += 1
|
|
||||||
continue
|
|
||||||
elif c == '.':
|
|
||||||
if token:
|
|
||||||
tokens.append(token)
|
|
||||||
token = ''
|
|
||||||
i += 1
|
|
||||||
continue
|
|
||||||
elif c == '[':
|
|
||||||
if token:
|
|
||||||
tokens.append(token)
|
|
||||||
token = ''
|
|
||||||
idx = ''
|
|
||||||
while i < length and path[i] != ']':
|
|
||||||
idx += path[i]
|
|
||||||
i += 1
|
|
||||||
if i < length and path[i] == ']':
|
|
||||||
idx += ']'
|
|
||||||
i += 1
|
|
||||||
tokens.append(idx)
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
token += c
|
|
||||||
i += 1
|
|
||||||
if token:
|
|
||||||
tokens.append(token)
|
|
||||||
return tokens
|
|
||||||
|
|
||||||
|
def is_lazy_item(item: QTreeWidgetItem) -> bool:
|
||||||
def is_lazy_item(item):
|
|
||||||
return item.childCount() == 1 and item.child(0).text(0) == 'lazy_marker'
|
return item.childCount() == 1 and item.child(0).text(0) == 'lazy_marker'
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# VariableSelectWidget
|
||||||
|
# ------------------------------------------------------------------
|
||||||
class VariableSelectWidget(QWidget):
|
class VariableSelectWidget(QWidget):
|
||||||
|
"""
|
||||||
|
Виджет выбора переменных с деревом + строкой поиска + автодополнением.
|
||||||
|
Подсказки полностью через PathHints.
|
||||||
|
ВАЖНО: ожидается, что в данных (vars_list) каждое var['name'] — ПОЛНЫЙ ПУТЬ
|
||||||
|
(например: 'project.adc.status'), даже внутри children.
|
||||||
|
"""
|
||||||
|
|
||||||
|
ROLE_NAME = Qt.UserRole # локальный хвост (display)
|
||||||
|
ROLE_VAR_DICT = Qt.UserRole + 100 # исходный dict
|
||||||
|
ROLE_FULLPATH = Qt.UserRole + 200 # полный путь
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.expanded_vars = []
|
|
||||||
self.node_index = {}
|
|
||||||
self.is_autocomplete_on = True # <--- ДОБАВИТЬ ЭТУ СТРОКУ
|
|
||||||
self._bckspc_pressed = False
|
|
||||||
self.manual_completion_active = False
|
|
||||||
self._vars_hash = None
|
|
||||||
|
|
||||||
# --- UI Элементы ---
|
# данные
|
||||||
|
self.expanded_vars: List[Dict[str, Any]] = []
|
||||||
|
self.is_autocomplete_on = True
|
||||||
|
self.manual_completion_active = False
|
||||||
|
self._bckspc_pressed = False
|
||||||
|
self._vars_hash: Optional[str] = None
|
||||||
|
|
||||||
|
# индекс: canonical_full_path -> item
|
||||||
|
self._item_by_canon: Dict[str, QTreeWidgetItem] = {}
|
||||||
|
|
||||||
|
# подсказки
|
||||||
|
self.hints = PathHints()
|
||||||
|
|
||||||
|
# --- UI ---
|
||||||
self.search_input = QLineEdit(self)
|
self.search_input = QLineEdit(self)
|
||||||
self.search_input.setPlaceholderText("Поиск...")
|
self.search_input.setPlaceholderText("Поиск...")
|
||||||
|
|
||||||
self.tree = QTreeWidget(self)
|
self.tree = QTreeWidget(self)
|
||||||
self.tree.setHeaderLabels(["Имя переменной", "Тип"])
|
self.tree.setHeaderLabels(["Имя переменной", "Тип"])
|
||||||
self.tree.setSelectionMode(QTreeWidget.ExtendedSelection)
|
self.tree.setSelectionMode(QTreeWidget.ExtendedSelection)
|
||||||
@ -97,32 +75,58 @@ class VariableSelectWidget(QWidget):
|
|||||||
self.completer.setCaseSensitivity(Qt.CaseInsensitive)
|
self.completer.setCaseSensitivity(Qt.CaseInsensitive)
|
||||||
self.completer.setFilterMode(Qt.MatchContains)
|
self.completer.setFilterMode(Qt.MatchContains)
|
||||||
self.completer.setWidget(self.search_input)
|
self.completer.setWidget(self.search_input)
|
||||||
|
self.completer.activated[str].connect(self.insert_completion)
|
||||||
|
|
||||||
# --- Layout ---
|
# layout
|
||||||
layout = QVBoxLayout(self)
|
lay = QVBoxLayout(self)
|
||||||
layout.setContentsMargins(0, 0, 0, 0)
|
lay.setContentsMargins(0, 0, 0, 0)
|
||||||
layout.addWidget(self.search_input)
|
lay.addWidget(self.search_input)
|
||||||
layout.addWidget(self.tree)
|
lay.addWidget(self.tree)
|
||||||
|
|
||||||
# --- Соединения ---
|
# signals
|
||||||
#self.search_input.textChanged.connect(self.on_search_text_changed)
|
self.search_input.textChanged.connect(self.on_search_text_changed)
|
||||||
self.search_input.textChanged.connect(lambda text: self.on_search_text_changed(text))
|
|
||||||
self.search_input.installEventFilter(self)
|
self.search_input.installEventFilter(self)
|
||||||
self.completer.activated[str].connect(lambda text: self.insert_completion(text))
|
|
||||||
|
|
||||||
# --- Публичные методы для управления виджетом снаружи ---
|
# ------------------------------------------------------------------
|
||||||
|
# public api
|
||||||
|
# ------------------------------------------------------------------
|
||||||
def set_autocomplete(self, enabled: bool):
|
def set_autocomplete(self, enabled: bool):
|
||||||
"""Включает или выключает режим автодополнения."""
|
|
||||||
self.is_autocomplete_on = enabled
|
self.is_autocomplete_on = enabled
|
||||||
|
|
||||||
def set_data(self, vars_list):
|
def set_data(self, vars_list: List[Dict[str, Any]]):
|
||||||
"""Основной метод для загрузки данных в виджет."""
|
"""
|
||||||
|
Загружаем список переменных (формат: см. класс docstring).
|
||||||
|
"""
|
||||||
|
# deepcopy
|
||||||
self.expanded_vars = pickle.loads(pickle.dumps(vars_list, protocol=pickle.HIGHEST_PROTOCOL))
|
self.expanded_vars = pickle.loads(pickle.dumps(vars_list, protocol=pickle.HIGHEST_PROTOCOL))
|
||||||
# self.build_completion_list() # Если нужна полная перестройка списка
|
|
||||||
self.populate_tree()
|
|
||||||
|
|
||||||
|
# rebuild hints из полного списка узлов (каждый узел уже с full_path)
|
||||||
|
self._rebuild_hints_from_vars(self.expanded_vars)
|
||||||
|
|
||||||
|
# rebuild tree
|
||||||
|
self.populate_tree(self.expanded_vars)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# hints builder: дети уже содержат ПОЛНЫЙ ПУТЬ
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
def _rebuild_hints_from_vars(self, vars_list: List[Dict[str, Any]]):
|
||||||
|
paths: List[tuple] = []
|
||||||
|
|
||||||
|
def walk(node: Dict[str, Any]):
|
||||||
|
full = node.get('name', '')
|
||||||
|
if full:
|
||||||
|
paths.append((full, node.get('type')))
|
||||||
|
for ch in node.get('children', []) or []:
|
||||||
|
walk(ch)
|
||||||
|
|
||||||
|
for v in vars_list:
|
||||||
|
walk(v)
|
||||||
|
|
||||||
|
self.hints.set_paths(paths)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# tree building
|
||||||
|
# ------------------------------------------------------------------
|
||||||
def populate_tree(self, vars_list=None):
|
def populate_tree(self, vars_list=None):
|
||||||
if vars_list is None:
|
if vars_list is None:
|
||||||
vars_list = self.expanded_vars
|
vars_list = self.expanded_vars
|
||||||
@ -130,477 +134,295 @@ class VariableSelectWidget(QWidget):
|
|||||||
new_hash = compute_vars_hash(vars_list)
|
new_hash = compute_vars_hash(vars_list)
|
||||||
if self._vars_hash == new_hash:
|
if self._vars_hash == new_hash:
|
||||||
return
|
return
|
||||||
|
|
||||||
self._vars_hash = new_hash
|
self._vars_hash = new_hash
|
||||||
|
|
||||||
self.tree.setUpdatesEnabled(False)
|
self.tree.setUpdatesEnabled(False)
|
||||||
self.tree.blockSignals(True)
|
self.tree.blockSignals(True)
|
||||||
self.tree.clear()
|
self.tree.clear()
|
||||||
self.node_index.clear()
|
self._item_by_canon.clear()
|
||||||
|
|
||||||
for var in vars_list:
|
# построим top-level из входного списка: определяем по глубине токенов
|
||||||
self.add_tree_item_lazy(None, var)
|
# (vars_list может содержать и глубокие узлы; выберем корни = те, чей full_path не имеет родителя в списке)
|
||||||
|
full_to_node = {v['name']: v for v in vars_list}
|
||||||
|
# но safer: просто добавляем все как top-level, если ты уже передаёшь только корни.
|
||||||
|
# Если в твоих данных vars_list == корни, просто сделаем:
|
||||||
|
for v in vars_list:
|
||||||
|
self._add_tree_item_lazy(None, v)
|
||||||
|
|
||||||
self.tree.setUpdatesEnabled(True)
|
self.tree.setUpdatesEnabled(True)
|
||||||
self.tree.blockSignals(False)
|
self.tree.blockSignals(False)
|
||||||
|
|
||||||
header = self.tree.header()
|
header = self.tree.header()
|
||||||
header.setSectionResizeMode(QHeaderView.Interactive)
|
header.setSectionResizeMode(QHeaderView.Interactive)
|
||||||
header.setSectionResizeMode(1, QHeaderView.Stretch)
|
header.setSectionResizeMode(1, QHeaderView.Stretch)
|
||||||
self.tree.setColumnWidth(0, 400)
|
self.tree.setColumnWidth(0, 400)
|
||||||
|
|
||||||
def on_item_expanded(self, item):
|
def on_item_expanded(self, item: QTreeWidgetItem):
|
||||||
if is_lazy_item(item):
|
if is_lazy_item(item):
|
||||||
item.removeChild(item.child(0))
|
item.removeChild(item.child(0))
|
||||||
var = item.data(0, Qt.UserRole + 100)
|
var = item.data(0, self.ROLE_VAR_DICT)
|
||||||
if var:
|
if var:
|
||||||
for child_var in var.get('children', []):
|
for ch in var.get('children', []) or []:
|
||||||
self.add_tree_item_lazy(item, child_var)
|
self._add_tree_item_lazy(item, ch)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
def get_full_item_name(self, item):
|
# item creation (var['name'] — ПОЛНЫЙ ПУТЬ)
|
||||||
fullname = item.text(0)
|
# ------------------------------------------------------------------
|
||||||
# Заменяем '->' на '.'
|
def _add_tree_item_lazy(self, parent: Optional[QTreeWidgetItem], var: Dict[str, Any]):
|
||||||
fullname = fullname.replace('->', '.')
|
full_path = var.get('name', '')
|
||||||
fullname = fullname.replace('[', '.[')
|
|
||||||
return fullname
|
|
||||||
|
|
||||||
def add_tree_item_lazy(self, parent, var):
|
|
||||||
name = var['name']
|
|
||||||
type_str = var.get('type', '')
|
type_str = var.get('type', '')
|
||||||
item = QTreeWidgetItem([name, type_str])
|
|
||||||
item.setData(0, Qt.UserRole, name)
|
# здесь оставляем полный путь для отображения
|
||||||
full_name = self.get_full_item_name(item)
|
item = QTreeWidgetItem([full_path, type_str])
|
||||||
self.node_index[full_name.lower()] = item
|
item.setData(0, self.ROLE_NAME, full_path) # теперь ROLE_NAME = полный путь
|
||||||
|
item.setData(0, self.ROLE_VAR_DICT, var)
|
||||||
|
item.setData(0, self.ROLE_FULLPATH, full_path)
|
||||||
|
|
||||||
if "(bitfield:" in type_str:
|
if "(bitfield:" in type_str:
|
||||||
item.setDisabled(True)
|
item.setDisabled(True)
|
||||||
self.set_tool(item, "Битовые поля недоступны для выбора")
|
self._set_tool(item, "Битовые поля недоступны для выбора")
|
||||||
|
|
||||||
|
# метаданные
|
||||||
for i, attr in enumerate(['file', 'extern', 'static']):
|
for i, attr in enumerate(['file', 'extern', 'static']):
|
||||||
item.setData(0, Qt.UserRole + 1 + i, var.get(attr))
|
item.setData(0, Qt.UserRole + 1 + i, var.get(attr))
|
||||||
|
|
||||||
|
# в дерево
|
||||||
if parent is None:
|
if parent is None:
|
||||||
self.tree.addTopLevelItem(item)
|
self.tree.addTopLevelItem(item)
|
||||||
else:
|
else:
|
||||||
parent.addChild(item)
|
parent.addChild(item)
|
||||||
|
|
||||||
# Если есть дети — добавляем заглушку (чтобы можно было раскрыть)
|
# lazy children
|
||||||
if var.get('children'):
|
if var.get('children'):
|
||||||
dummy = QTreeWidgetItem(["lazy_marker"])
|
dummy = QTreeWidgetItem(["lazy_marker"])
|
||||||
item.addChild(dummy)
|
item.addChild(dummy)
|
||||||
|
|
||||||
# Кэшируем детей для подгрузки по событию
|
# индекс
|
||||||
item.setData(0, Qt.UserRole + 100, var) # Сохраняем var целиком
|
self._item_by_canon[canonical_key(full_path)] = item
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
def show_matching_path(self, item, path_parts, level=0):
|
def _tail_token(full_path: str) -> str:
|
||||||
node_name = item.text(0).lower()
|
toks = split_path_tokens(full_path)
|
||||||
node_parts = split_path(node_name)
|
return toks[-1] if toks else full_path
|
||||||
|
|
||||||
if 'project' in node_name:
|
# ------------------------------------------------------------------
|
||||||
a = 1
|
# filtering
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
def filter_tree(self):
|
||||||
|
"""
|
||||||
|
Быстрый фильтр:
|
||||||
|
- без разделителей → substring по ЛОКАЛЬНОМУ имени top-level
|
||||||
|
- с разделителями → структурный (по токенам full_path)
|
||||||
|
"""
|
||||||
|
text = (self.search_input.text() or '').strip()
|
||||||
|
low = text.lower()
|
||||||
|
parts = split_path_tokens(low) if low else []
|
||||||
|
|
||||||
|
# простой режим (нет ., ->, [):
|
||||||
|
if low and all(x not in low for x in ('.', '->', '[')):
|
||||||
|
for i in range(self.tree.topLevelItemCount()):
|
||||||
|
it = self.tree.topLevelItem(i)
|
||||||
|
full = (it.data(0, self.ROLE_FULLPATH) or '').lower()
|
||||||
|
it.setHidden(low not in full)
|
||||||
|
return
|
||||||
|
|
||||||
|
# структурный
|
||||||
|
for i in range(self.tree.topLevelItemCount()):
|
||||||
|
it = self.tree.topLevelItem(i)
|
||||||
|
self._show_matching_path(it, parts, 0)
|
||||||
|
|
||||||
|
def _show_matching_path(self, item: QTreeWidgetItem, path_parts: List[str], level: int = 0):
|
||||||
|
"""
|
||||||
|
Сравниваем введённый путь (разбитый на токены) с ПОЛНЫМ ПУТЁМ узла.
|
||||||
|
Алгоритм: берём полный путь узла, разбиваем в токены, берём уровень level,
|
||||||
|
и сравниваем с соответствующим токеном path_parts[level].
|
||||||
|
"""
|
||||||
|
full = (item.data(0, self.ROLE_FULLPATH) or '').lower()
|
||||||
|
node_parts = split_path_tokens(full)
|
||||||
|
|
||||||
if level >= len(path_parts):
|
if level >= len(path_parts):
|
||||||
# Путь полностью пройден — показываем только этот узел (без раскрытия всех детей)
|
|
||||||
item.setHidden(False)
|
item.setHidden(False)
|
||||||
item.setExpanded(False)
|
item.setExpanded(False)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if level >= len(node_parts):
|
if level >= len(node_parts):
|
||||||
# Уровень поиска больше длины пути узла — скрываем
|
item.setHidden(True)
|
||||||
item.setHidden(False)
|
return False
|
||||||
|
|
||||||
search_part = path_parts[level]
|
search_part = path_parts[level]
|
||||||
node_part = node_parts[level]
|
node_part = node_parts[level]
|
||||||
|
|
||||||
if search_part == node_part:
|
if search_part == node_part:
|
||||||
# Точное совпадение — показываем узел, идём вглубь только по совпадениям
|
|
||||||
item.setHidden(False)
|
item.setHidden(False)
|
||||||
matched_any = False
|
matched_any = False
|
||||||
self.on_item_expanded(item)
|
self.on_item_expanded(item)
|
||||||
for i in range(item.childCount()):
|
for i in range(item.childCount()):
|
||||||
child = item.child(i)
|
ch = item.child(i)
|
||||||
if self.show_matching_path(child, path_parts, level + 1):
|
if self._show_matching_path(ch, path_parts, level + 1):
|
||||||
matched_any = True
|
matched_any = True
|
||||||
item.setExpanded(matched_any)
|
item.setExpanded(matched_any)
|
||||||
return matched_any or item.childCount() == 0
|
return matched_any or item.childCount() == 0
|
||||||
|
|
||||||
elif node_part.startswith(search_part):
|
elif node_part.startswith(search_part):
|
||||||
# Неполное совпадение — показываем только этот узел, детей скрываем, не раскрываем
|
|
||||||
item.setHidden(False)
|
item.setHidden(False)
|
||||||
item.setExpanded(False)
|
item.setExpanded(False)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
elif search_part in node_part and (level == len(path_parts)-1):
|
elif search_part in node_part and (level == len(path_parts) - 1):
|
||||||
# Неполное совпадение — показываем только этот узел, детей скрываем, не раскрываем
|
|
||||||
item.setHidden(False)
|
item.setHidden(False)
|
||||||
item.setExpanded(False)
|
item.setExpanded(False)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Несовпадение — скрываем
|
|
||||||
item.setHidden(True)
|
item.setHidden(True)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
def filter_tree(self):
|
# completions (ONLY PathHints)
|
||||||
text = self.search_input.text().strip().lower()
|
# ------------------------------------------------------------------
|
||||||
path_parts = split_path(text) if text else []
|
def update_completions(self, text: Optional[str] = None) -> List[str]:
|
||||||
|
|
||||||
if '.' not in text and '->' 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)
|
|
||||||
# Не сбрасываем expanded, чтобы можно было раскрывать вручную
|
|
||||||
else:
|
|
||||||
item.setHidden(True)
|
|
||||||
else:
|
|
||||||
for i in range(self.tree.topLevelItemCount()):
|
|
||||||
item = self.tree.topLevelItem(i)
|
|
||||||
self.show_matching_path(item, path_parts, 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):
|
|
||||||
if text is None:
|
if text is None:
|
||||||
text = self.search_input.text().strip()
|
text = self.search_input.text()
|
||||||
|
suggestions = self.hints.suggest(text)
|
||||||
|
self.completer.setModel(QStringListModel(suggestions))
|
||||||
|
if suggestions:
|
||||||
|
self.completer.complete()
|
||||||
else:
|
else:
|
||||||
text = text.strip()
|
self.completer.popup().hide()
|
||||||
|
return suggestions
|
||||||
|
|
||||||
normalized_text = text.replace('->', '.')
|
def insert_completion(self, full_path: str):
|
||||||
parts = split_path(text)
|
text = self.hints.add_separator(full_path)
|
||||||
path_parts = parts[:-1] if parts else []
|
if not self._bckspc_pressed:
|
||||||
prefix = parts[-1].lower() if parts else ''
|
|
||||||
ends_with_sep = text.endswith('.') or text.endswith('->') or text.endswith('[')
|
|
||||||
is_index_suggestion = text.endswith('[')
|
|
||||||
|
|
||||||
completions = []
|
|
||||||
|
|
||||||
def find_exact_node(parts):
|
|
||||||
if not parts:
|
|
||||||
return None
|
|
||||||
fullname = parts[0]
|
|
||||||
for p in parts[1:]:
|
|
||||||
fullname += '.' + p
|
|
||||||
return self.node_index.get(fullname.lower())
|
|
||||||
|
|
||||||
if is_index_suggestion:
|
|
||||||
base_text = text[:-1] # убираем '['
|
|
||||||
parent_node = self.find_node_by_fullname(base_text)
|
|
||||||
if not parent_node:
|
|
||||||
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)
|
|
||||||
if child.isHidden():
|
|
||||||
continue
|
|
||||||
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
|
|
||||||
|
|
||||||
if ends_with_sep:
|
|
||||||
node = self.find_node_by_fullname(text[:-1])
|
|
||||||
if node:
|
|
||||||
for i in range(node.childCount()):
|
|
||||||
child = node.child(i)
|
|
||||||
if child.isHidden():
|
|
||||||
continue
|
|
||||||
completions.append(child.text(0))
|
|
||||||
elif not path_parts:
|
|
||||||
# Первый уровень — только если имя начинается с prefix
|
|
||||||
for i in range(self.tree.topLevelItemCount()):
|
|
||||||
item = self.tree.topLevelItem(i)
|
|
||||||
if item.isHidden():
|
|
||||||
continue
|
|
||||||
name = item.text(0)
|
|
||||||
if name.lower().startswith(prefix):
|
|
||||||
completions.append(name)
|
|
||||||
else:
|
|
||||||
node = find_exact_node(path_parts)
|
|
||||||
if node:
|
|
||||||
for i in range(node.childCount()):
|
|
||||||
child = node.child(i)
|
|
||||||
if child.isHidden():
|
|
||||||
continue
|
|
||||||
name = child.text(0)
|
|
||||||
name_parts = child.data(0, Qt.UserRole + 10)
|
|
||||||
if name_parts is None:
|
|
||||||
name_parts = 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: # ← строго startswith
|
|
||||||
completions.append(name)
|
|
||||||
|
|
||||||
self.completer.setModel(QStringListModel(completions))
|
|
||||||
self.completer.complete()
|
|
||||||
return completions
|
|
||||||
|
|
||||||
|
|
||||||
# Функция для поиска узла с полным именем
|
|
||||||
def find_node_by_fullname(self, name):
|
|
||||||
if name is None:
|
|
||||||
return None
|
|
||||||
normalized_name = name.replace('->', '.').lower()
|
|
||||||
normalized_name = normalized_name.replace('[', '.[').lower()
|
|
||||||
return self.node_index.get(normalized_name)
|
|
||||||
|
|
||||||
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('->') or text.endswith('[')):
|
|
||||||
# Определяем разделитель по имени первого ребёнка
|
|
||||||
child_name = node.child(0).text(0)
|
|
||||||
if child_name.startswith(text + '->'):
|
|
||||||
text += '->'
|
|
||||||
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.run_completions(text)
|
|
||||||
else:
|
|
||||||
self.search_input.setText(text)
|
self.search_input.setText(text)
|
||||||
self.search_input.setCursorPosition(len(text))
|
self.search_input.setCursorPosition(len(text))
|
||||||
|
self.run_completions(text)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# events
|
||||||
|
# ------------------------------------------------------------------
|
||||||
def eventFilter(self, obj, event):
|
def eventFilter(self, obj, event):
|
||||||
if obj == self.search_input and isinstance(event, QKeyEvent):
|
if obj == self.search_input and isinstance(event, QKeyEvent):
|
||||||
if event.key() == Qt.Key_Space and event.modifiers() & Qt.ControlModifier:
|
if event.key() == Qt.Key_Space and event.modifiers() & Qt.ControlModifier:
|
||||||
self.manual_completion_active = True
|
self.manual_completion_active = True
|
||||||
text = self.search_input.text().strip()
|
self.run_completions(self.search_input.text())
|
||||||
self.run_completions(text)
|
|
||||||
elif event.key() == Qt.Key_Escape:
|
elif event.key() == Qt.Key_Escape:
|
||||||
# Esc — выключаем ручной режим и скрываем подсказки, если autocomplete выключен
|
|
||||||
if not self.is_autocomplete_on:
|
if not self.is_autocomplete_on:
|
||||||
self.manual_completion_active = False
|
self.manual_completion_active = False
|
||||||
self.completer.popup().hide()
|
self.completer.popup().hide()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if event.key() == Qt.Key_Backspace:
|
if event.key() == Qt.Key_Backspace:
|
||||||
self._bckspc_pressed = True
|
self._bckspc_pressed = True
|
||||||
else:
|
else:
|
||||||
self._bckspc_pressed = False
|
self._bckspc_pressed = False
|
||||||
|
|
||||||
return super().eventFilter(obj, event)
|
return super().eventFilter(obj, event)
|
||||||
|
|
||||||
def run_completions(self, text):
|
|
||||||
completions = self.update_completions(text)
|
|
||||||
|
|
||||||
if not self.is_autocomplete_on and self._bckspc_pressed:
|
def run_completions(self, text: str):
|
||||||
text = text[:-1]
|
if not self.is_autocomplete_on and not self.manual_completion_active:
|
||||||
|
self.completer.popup().hide()
|
||||||
if len(completions) == 1 and completions[0].lower() == text.lower():
|
return
|
||||||
# Найдем узел с таким именем
|
self.update_completions(text)
|
||||||
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])
|
def on_search_text_changed(self, text: str):
|
||||||
if node and node.childCount() > 0:
|
self.completer.setWidget(self.search_input)
|
||||||
# Используем первую подсказку, чтобы определить нужный разделитель
|
|
||||||
completions = self.update_completions(text + '.')
|
|
||||||
if not completions:
|
|
||||||
return
|
|
||||||
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):
|
|
||||||
sender_widget = self.sender()
|
|
||||||
sender_name = sender_widget.objectName() if sender_widget else "Unknown Sender"
|
|
||||||
|
|
||||||
self.completer.setWidget(self.search_input)
|
|
||||||
self.filter_tree()
|
self.filter_tree()
|
||||||
if text == None:
|
if text is None:
|
||||||
text = self.search_input.text().strip()
|
text = self.search_input.text()
|
||||||
if self.is_autocomplete_on:
|
if self.is_autocomplete_on:
|
||||||
self.run_completions(text)
|
self.run_completions(text)
|
||||||
else:
|
else:
|
||||||
# Если выключено, показываем подсказки только если флаг ручного вызова True
|
|
||||||
if self.manual_completion_active:
|
if self.manual_completion_active:
|
||||||
self.run_completions(text)
|
self.run_completions(text)
|
||||||
else:
|
else:
|
||||||
self.completer.popup().hide()
|
self.completer.popup().hide()
|
||||||
|
|
||||||
def focusInEvent(self, event):
|
def focusInEvent(self, event):
|
||||||
if self.completer.widget() != self.search_input:
|
if self.completer.widget() != self.search_input:
|
||||||
self.completer.setWidget(self.search_input)
|
self.completer.setWidget(self.search_input)
|
||||||
super().focusInEvent(event)
|
super().focusInEvent(event)
|
||||||
|
|
||||||
def _custom_focus_in_event(self, event):
|
|
||||||
# Принудительно установить виджет для completer при получении фокуса
|
|
||||||
if self.completer.widget() != self.search_input:
|
|
||||||
self.completer.setWidget(self.search_input)
|
|
||||||
super(QLineEdit, self.search_input).focusInEvent(event) # Вызвать оригинальный обработчик
|
|
||||||
|
|
||||||
|
|
||||||
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 set_tool(self, item, text):
|
|
||||||
item.setToolTip(0, text)
|
|
||||||
item.setToolTip(1, text)
|
|
||||||
|
|
||||||
def get_all_items(self):
|
|
||||||
"""Возвращает все конечные (leaf) элементы, исключая битовые поля и элементы с детьми (реальными)."""
|
|
||||||
def collect_leaf_items(parent):
|
|
||||||
leaf_items = []
|
|
||||||
for i in range(parent.childCount()):
|
|
||||||
child = parent.child(i)
|
|
||||||
if child.isHidden():
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Если есть заглушка — раскрываем
|
|
||||||
self.on_item_expanded(child)
|
|
||||||
|
|
||||||
if child.childCount() == 0:
|
|
||||||
item_type = child.text(1)
|
|
||||||
if item_type and 'bitfield' in str(item_type).lower():
|
|
||||||
continue
|
|
||||||
leaf_items.append(child)
|
|
||||||
else:
|
|
||||||
leaf_items.extend(collect_leaf_items(child))
|
|
||||||
return leaf_items
|
|
||||||
|
|
||||||
all_leaf_items = []
|
|
||||||
for i in range(self.tree.topLevelItemCount()):
|
|
||||||
top = self.tree.topLevelItem(i)
|
|
||||||
|
|
||||||
# Раскрываем lazy, если надо
|
|
||||||
self.on_item_expanded(top)
|
|
||||||
|
|
||||||
if top.childCount() == 0:
|
|
||||||
item_type = top.text(1)
|
|
||||||
if item_type and 'bitfield' in str(item_type).lower():
|
|
||||||
continue
|
|
||||||
all_leaf_items.append(top)
|
|
||||||
else:
|
|
||||||
all_leaf_items.extend(collect_leaf_items(top))
|
|
||||||
return all_leaf_items
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _get_internal_selected_items(self):
|
|
||||||
"""Возвращает выделенные элементы и всех их потомков, включая lazy."""
|
|
||||||
selected = self.tree.selectedItems()
|
|
||||||
all_items = []
|
|
||||||
|
|
||||||
def collect_children(item):
|
|
||||||
# Раскрываем при необходимости
|
|
||||||
# Раскрываем lazy, если надо
|
|
||||||
self.on_item_expanded(item)
|
|
||||||
|
|
||||||
items = [item]
|
|
||||||
for i in range(item.childCount()):
|
|
||||||
child = item.child(i)
|
|
||||||
items.extend(collect_children(child))
|
|
||||||
return items
|
|
||||||
|
|
||||||
for item in selected:
|
|
||||||
all_items.extend(collect_children(item))
|
|
||||||
|
|
||||||
return all_items
|
|
||||||
|
|
||||||
def get_selected_items(self):
|
|
||||||
"""Возвращает только конечные (leaf) выделенные элементы, исключая bitfield."""
|
|
||||||
selected = self.tree.selectedItems()
|
|
||||||
leaf_items = []
|
|
||||||
for item in selected:
|
|
||||||
# Раскрываем lazy, если надо
|
|
||||||
self.on_item_expanded(item)
|
|
||||||
|
|
||||||
# Если у узла нет видимых/выделенных детей — он лист
|
|
||||||
if all(item.child(i).isHidden() or not item.child(i).isSelected() for i in range(item.childCount())):
|
|
||||||
item_type = item.data(0, Qt.UserRole)
|
|
||||||
if item_type and 'bitfield' in str(item_type).lower():
|
|
||||||
continue
|
|
||||||
leaf_items.append(item)
|
|
||||||
return leaf_items
|
|
||||||
|
|
||||||
|
|
||||||
def get_all_var_names(self):
|
|
||||||
"""Возвращает имена всех конечных (leaf) переменных, исключая битовые поля и группы."""
|
|
||||||
return [item.text(0) for item in self.get_all_items() if item.text(0)]
|
|
||||||
|
|
||||||
|
|
||||||
def _get_internal_selected_var_names(self):
|
|
||||||
"""Возвращает имена выделенных переменных."""
|
|
||||||
return [item.text(0) for item in self._get_internal_selected_items() if item.text(0)]
|
|
||||||
|
|
||||||
|
|
||||||
def get_selected_var_names(self):
|
|
||||||
"""Возвращает имена только конечных (leaf) переменных из выделенных."""
|
|
||||||
return [item.text(0) for item in self.get_selected_items() if item.text(0)]
|
|
||||||
|
|
||||||
def closeEvent(self, event):
|
def closeEvent(self, event):
|
||||||
self.completer.setWidget(None)
|
self.completer.setWidget(None)
|
||||||
self.completer.deleteLater()
|
self.completer.deleteLater()
|
||||||
super().closeEvent(event)
|
super().closeEvent(event)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# lookup by full path
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
def find_item_by_fullpath(self, path: str) -> Optional[QTreeWidgetItem]:
|
||||||
|
return self._item_by_canon.get(canonical_key(path))
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# tooltips
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
def _set_tool(self, item: QTreeWidgetItem, text: str):
|
||||||
|
item.setToolTip(0, text)
|
||||||
|
item.setToolTip(1, text)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# selection helpers
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
def get_all_items(self):
|
||||||
|
"""Все leaf-узлы (подгружаем lazy)."""
|
||||||
|
def collect_leaf(parent):
|
||||||
|
leaves = []
|
||||||
|
for i in range(parent.childCount()):
|
||||||
|
ch = parent.child(i)
|
||||||
|
if ch.isHidden():
|
||||||
|
continue
|
||||||
|
self.on_item_expanded(ch)
|
||||||
|
if ch.childCount() == 0:
|
||||||
|
t = ch.text(1)
|
||||||
|
if t and 'bitfield' in t.lower():
|
||||||
|
continue
|
||||||
|
leaves.append(ch)
|
||||||
|
else:
|
||||||
|
leaves.extend(collect_leaf(ch))
|
||||||
|
return leaves
|
||||||
|
|
||||||
|
out = []
|
||||||
|
for i in range(self.tree.topLevelItemCount()):
|
||||||
|
top = self.tree.topLevelItem(i)
|
||||||
|
self.on_item_expanded(top)
|
||||||
|
if top.childCount() == 0:
|
||||||
|
t = top.text(1)
|
||||||
|
if t and 'bitfield' in t.lower():
|
||||||
|
continue
|
||||||
|
out.append(top)
|
||||||
|
else:
|
||||||
|
out.extend(collect_leaf(top))
|
||||||
|
return out
|
||||||
|
|
||||||
|
def _get_internal_selected_items(self):
|
||||||
|
selected = self.tree.selectedItems()
|
||||||
|
all_items = []
|
||||||
|
def collect(item):
|
||||||
|
self.on_item_expanded(item)
|
||||||
|
res = [item]
|
||||||
|
for i in range(item.childCount()):
|
||||||
|
res.extend(collect(item.child(i)))
|
||||||
|
return res
|
||||||
|
for it in selected:
|
||||||
|
all_items.extend(collect(it))
|
||||||
|
return all_items
|
||||||
|
|
||||||
|
def get_selected_items(self):
|
||||||
|
selected = self.tree.selectedItems()
|
||||||
|
leaves = []
|
||||||
|
for it in selected:
|
||||||
|
self.on_item_expanded(it)
|
||||||
|
if all(it.child(i).isHidden() or not it.child(i).isSelected() for i in range(it.childCount())):
|
||||||
|
t = it.data(0, self.ROLE_NAME)
|
||||||
|
if t and isinstance(t, str) and 'bitfield' in t.lower():
|
||||||
|
continue
|
||||||
|
leaves.append(it)
|
||||||
|
return leaves
|
||||||
|
|
||||||
|
def get_all_var_names(self):
|
||||||
|
return [it.data(0, self.ROLE_FULLPATH) for it in self.get_all_items() if it.data(0, self.ROLE_FULLPATH)]
|
||||||
|
|
||||||
|
def _get_internal_selected_var_names(self):
|
||||||
|
return [it.data(0, self.ROLE_FULLPATH) for it in self._get_internal_selected_items() if it.data(0, self.ROLE_FULLPATH)]
|
||||||
|
|
||||||
|
def get_selected_var_names(self):
|
||||||
|
return [it.data(0, self.ROLE_FULLPATH) for it in self.get_selected_items() if it.data(0, self.ROLE_FULLPATH)]
|
||||||
|
@ -304,7 +304,7 @@ class VariableSelectorDialog(QDialog):
|
|||||||
# Проверка пути к XML
|
# Проверка пути к XML
|
||||||
if not hasattr(self, 'xml_path') or not self.xml_path:
|
if not hasattr(self, 'xml_path') or not self.xml_path:
|
||||||
from PySide2.QtWidgets import QMessageBox
|
from PySide2.QtWidgets import QMessageBox
|
||||||
QMessageBox.warning(self, "Ошибка", "Путь к XML не задан, невозможно обновить переменные.")
|
#QMessageBox.warning(self, "Ошибка", "Путь к XML не задан, невозможно обновить переменные.")
|
||||||
return
|
return
|
||||||
|
|
||||||
root, tree = myXML.safe_parse_xml(self.xml_path)
|
root, tree = myXML.safe_parse_xml(self.xml_path)
|
||||||
|
182
Src/var_table.py
182
Src/var_table.py
@ -1,10 +1,10 @@
|
|||||||
from PySide2.QtWidgets import (
|
from PySide2.QtWidgets import (
|
||||||
QTableWidget, QTableWidgetItem, QCheckBox, QComboBox, QLineEdit, QCompleter,
|
QTableWidget, QTableWidgetItem, QCheckBox, QComboBox, QLineEdit, QCompleter,
|
||||||
QAbstractItemView, QHeaderView, QLabel,
|
QAbstractItemView, QHeaderView, QLabel, QSpacerItem, QSizePolicy, QSpinBox,
|
||||||
QDialog, QVBoxLayout, QHBoxLayout, QPushButton, QScrollArea, QWidget
|
QDialog, QVBoxLayout, QHBoxLayout, QPushButton, QScrollArea, QWidget
|
||||||
)
|
)
|
||||||
from PySide2.QtGui import QColor, QBrush, QPalette
|
from PySide2.QtGui import QColor, QBrush, QPalette
|
||||||
from PySide2.QtCore import Qt
|
from PySide2.QtCore import Qt, QSettings
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
from generate_debug_vars import type_map
|
from generate_debug_vars import type_map
|
||||||
import time
|
import time
|
||||||
@ -60,6 +60,57 @@ class FilterDialog(QDialog):
|
|||||||
return [cb.text() for cb in self.checkboxes if cb.isChecked()]
|
return [cb.text() for cb in self.checkboxes if cb.isChecked()]
|
||||||
|
|
||||||
|
|
||||||
|
class SetSizeDialog(QDialog):
|
||||||
|
"""
|
||||||
|
Диалоговое окно для выбора числового значения (размера).
|
||||||
|
"""
|
||||||
|
def __init__(self, parent=None, initial_value=10, min_value=1, max_value=50, title="Укажите размер короткого имени"):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.setWindowTitle(title)
|
||||||
|
self.setFixedSize(320, 120) # Задаем фиксированный размер для аккуратного вида
|
||||||
|
|
||||||
|
# Основной вертикальный макет
|
||||||
|
main_layout = QVBoxLayout(self)
|
||||||
|
|
||||||
|
# Макет для ввода значения
|
||||||
|
input_layout = QHBoxLayout()
|
||||||
|
label = QLabel("Количество символов:", self)
|
||||||
|
|
||||||
|
self.spin_box = QSpinBox(self)
|
||||||
|
self.spin_box.setRange(min_value, max_value) # Устанавливаем диапазон допустимых значений
|
||||||
|
initial_value = parent._shortname_size
|
||||||
|
self.spin_box.setValue(initial_value) # Устанавливаем начальное значение
|
||||||
|
self.spin_box.setFocus() # Устанавливаем фокус на поле ввода
|
||||||
|
|
||||||
|
input_layout.addWidget(label)
|
||||||
|
input_layout.addWidget(self.spin_box)
|
||||||
|
main_layout.addLayout(input_layout)
|
||||||
|
|
||||||
|
# Добавляем пустое пространство для лучшего разделения
|
||||||
|
main_layout.addSpacerItem(QSpacerItem(20, 20, QSizePolicy.Minimum, QSizePolicy.Expanding))
|
||||||
|
|
||||||
|
# Макет для кнопок
|
||||||
|
btn_layout = QHBoxLayout()
|
||||||
|
btn_layout.addStretch() # Добавляем растягивающийся элемент, чтобы кнопки были справа
|
||||||
|
|
||||||
|
btn_ok = QPushButton("OK")
|
||||||
|
btn_cancel = QPushButton("Отмена")
|
||||||
|
|
||||||
|
btn_layout.addWidget(btn_ok)
|
||||||
|
btn_layout.addWidget(btn_cancel)
|
||||||
|
|
||||||
|
main_layout.addLayout(btn_layout)
|
||||||
|
|
||||||
|
# Подключение сигналов к слотам
|
||||||
|
btn_ok.clicked.connect(self.accept) # При нажатии "OK" диалог закроется со статусом "Accepted"
|
||||||
|
btn_cancel.clicked.connect(self.reject) # При нажатии "Отмена" - со статусом "Rejected"
|
||||||
|
|
||||||
|
def get_selected_size(self):
|
||||||
|
"""
|
||||||
|
Возвращает значение, выбранное в QSpinBox.
|
||||||
|
"""
|
||||||
|
return self.spin_box.value()
|
||||||
|
|
||||||
class CtrlScrollComboBox(QComboBox):
|
class CtrlScrollComboBox(QComboBox):
|
||||||
def wheelEvent(self, event):
|
def wheelEvent(self, event):
|
||||||
if event.modifiers() & Qt.ControlModifier:
|
if event.modifiers() & Qt.ControlModifier:
|
||||||
@ -68,79 +119,89 @@ class CtrlScrollComboBox(QComboBox):
|
|||||||
event.ignore()
|
event.ignore()
|
||||||
|
|
||||||
class VariableTableWidget(QTableWidget):
|
class VariableTableWidget(QTableWidget):
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None, show_value_instead_of_shortname=0):
|
||||||
super().__init__(0, 8, parent)
|
|
||||||
# Таблица переменных
|
# Таблица переменных
|
||||||
self.setHorizontalHeaderLabels([
|
if show_value_instead_of_shortname:
|
||||||
'№', # новый столбец
|
super().__init__(0, 8, parent)
|
||||||
'En',
|
self.setHorizontalHeaderLabels([
|
||||||
'Name',
|
'№',
|
||||||
'Origin Type',
|
'En',
|
||||||
'Base Type',
|
'Name',
|
||||||
'IQ Type',
|
'Origin Type',
|
||||||
'Return Type',
|
'Base Type',
|
||||||
'Short Name'
|
'IQ Type',
|
||||||
])
|
'Return Type',
|
||||||
|
'Value'
|
||||||
|
])
|
||||||
|
self._show_value = True
|
||||||
|
else:
|
||||||
|
super().__init__(0, 8, parent)
|
||||||
|
self.setHorizontalHeaderLabels([
|
||||||
|
'№',
|
||||||
|
'En',
|
||||||
|
'Name',
|
||||||
|
'Origin Type',
|
||||||
|
'Base Type',
|
||||||
|
'IQ Type',
|
||||||
|
'Return Type',
|
||||||
|
'Short Name'
|
||||||
|
])
|
||||||
|
self._show_value = False
|
||||||
self.setEditTriggers(QAbstractItemView.AllEditTriggers)
|
self.setEditTriggers(QAbstractItemView.AllEditTriggers)
|
||||||
self.var_list = []
|
self.var_list = []
|
||||||
|
|
||||||
|
# QSettings
|
||||||
|
self.settings = QSettings("SET", "DebugVarEdit_VarTable")
|
||||||
|
shortsize = self.settings.value("shortname_size", True, type=int)
|
||||||
|
self._shortname_size = shortsize
|
||||||
|
|
||||||
self.type_options = list(dict.fromkeys(type_map.values()))
|
self.type_options = list(dict.fromkeys(type_map.values()))
|
||||||
self.pt_types_all = [t.replace('pt_', '') for t in self.type_options]
|
self.pt_types_all = [t.replace('pt_', '') for t in self.type_options]
|
||||||
self.iq_types_all = ['iq_none', 'iq'] + [f'iq{i}' for i in range(1, 31)]
|
self.iq_types_all = ['iq_none', 'iq'] + [f'iq{i}' for i in range(1, 31)]
|
||||||
# Задаём базовые iq-типы (без префикса 'iq_')
|
|
||||||
self.iq_types = ['iq_none', 'iq', 'iq10', 'iq15', 'iq19', 'iq24']
|
self.iq_types = ['iq_none', 'iq', 'iq10', 'iq15', 'iq19', 'iq24']
|
||||||
# Фильтруем типы из type_map.values() исключая те, что содержат 'arr' или 'ptr'
|
type_options = [t for t in dict.fromkeys(type_map.values()) if 'arr' not in t and 'ptr' not in t
|
||||||
type_options = [t for t in dict.fromkeys(type_map.values()) if 'arr' not in t and 'ptr' not in t and
|
and 'struct' not in t and 'union' not in t and '64' not in t]
|
||||||
'struct' not in t and 'union' not in t and '64' not in t]
|
|
||||||
# Формируем display_type_options без префикса 'pt_'
|
|
||||||
self.pt_types = [t.replace('pt_', '') for t in type_options]
|
self.pt_types = [t.replace('pt_', '') for t in type_options]
|
||||||
|
|
||||||
self._iq_type_filter = list(self.iq_types) # Текущий фильтр iq типов (по умолчанию все)
|
self._iq_type_filter = list(self.iq_types)
|
||||||
self._pt_type_filter = list(self.pt_types)
|
self._pt_type_filter = list(self.pt_types)
|
||||||
self._ret_type_filter = list(self.iq_types)
|
self._ret_type_filter = list(self.iq_types)
|
||||||
header = self.horizontalHeader()
|
|
||||||
# Для остальных колонок — растяжение (Stretch), чтобы они заняли всю оставшуюся ширину
|
|
||||||
|
|
||||||
|
header = self.horizontalHeader()
|
||||||
for col in range(self.columnCount()):
|
for col in range(self.columnCount()):
|
||||||
if col == self.columnCount() - 1:
|
if col == self.columnCount() - 1:
|
||||||
header.setSectionResizeMode(col, QHeaderView.Stretch)
|
header.setSectionResizeMode(col, QHeaderView.Stretch)
|
||||||
else:
|
else:
|
||||||
header.setSectionResizeMode(col, QHeaderView.Interactive)
|
header.setSectionResizeMode(col, QHeaderView.Interactive)
|
||||||
|
|
||||||
parent_widget = self.parentWidget()
|
|
||||||
# Сделаем колонки с номерами фиксированной ширины
|
|
||||||
self.setColumnWidth(rows.No, 30)
|
self.setColumnWidth(rows.No, 30)
|
||||||
self.setColumnWidth(rows.include, 30)
|
self.setColumnWidth(rows.include, 30)
|
||||||
self.setColumnWidth(rows.pt_type, 85)
|
self.setColumnWidth(rows.pt_type, 85)
|
||||||
self.setColumnWidth(rows.iq_type, 85)
|
self.setColumnWidth(rows.iq_type, 85)
|
||||||
self.setColumnWidth(rows.ret_type, 85)
|
self.setColumnWidth(rows.ret_type, 85)
|
||||||
|
|
||||||
self.setColumnWidth(rows.name, 300)
|
self.setColumnWidth(rows.name, 300)
|
||||||
self.setColumnWidth(rows.type, 100)
|
self.setColumnWidth(rows.type, 100)
|
||||||
|
|
||||||
self._resizing = False
|
self._resizing = False
|
||||||
self.horizontalHeader().sectionResized.connect(self.on_section_resized)
|
self.horizontalHeader().sectionResized.connect(self.on_section_resized)
|
||||||
self.horizontalHeader().sectionClicked.connect(self.on_header_clicked)
|
self.horizontalHeader().sectionClicked.connect(self.on_header_clicked)
|
||||||
|
|
||||||
|
|
||||||
def populate(self, vars_list, structs, on_change_callback):
|
def populate(self, vars_list, structs, on_change_callback):
|
||||||
self.var_list = vars_list
|
self.var_list = vars_list
|
||||||
|
self.setUpdatesEnabled(False)
|
||||||
|
self.blockSignals(True)
|
||||||
|
|
||||||
# --- ДО: удаляем отображение структур и union-переменных
|
|
||||||
for var in vars_list:
|
for var in vars_list:
|
||||||
pt_type = var.get('pt_type', '')
|
pt_type = var.get('pt_type', '')
|
||||||
if 'struct' in pt_type or 'union' in pt_type:
|
if 'struct' in pt_type or 'union' in pt_type:
|
||||||
var['show_var'] = 'false'
|
var['show_var'] = 'false'
|
||||||
var['enable'] = 'false'
|
var['enable'] = 'false'
|
||||||
|
|
||||||
|
|
||||||
filtered_vars = [v for v in vars_list if v.get('show_var', 'false') == 'true']
|
filtered_vars = [v for v in vars_list if v.get('show_var', 'false') == 'true']
|
||||||
self.setRowCount(len(filtered_vars))
|
self.setRowCount(len(filtered_vars))
|
||||||
self.verticalHeader().setVisible(False)
|
self.verticalHeader().setVisible(False)
|
||||||
style_with_padding = "padding-left: 5px; padding-right: 5px; font-size: 14pt; font-family: 'Segoe UI';"
|
style_with_padding = "padding-left: 5px; padding-right: 5px; font-size: 14pt; font-family: 'Segoe UI';"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
for row, var in enumerate(filtered_vars):
|
for row, var in enumerate(filtered_vars):
|
||||||
# №
|
# №
|
||||||
no_item = QTableWidgetItem(str(row))
|
no_item = QTableWidgetItem(str(row))
|
||||||
@ -156,25 +217,21 @@ class VariableTableWidget(QTableWidget):
|
|||||||
|
|
||||||
# Name
|
# Name
|
||||||
name_edit = QLineEdit(var['name'])
|
name_edit = QLineEdit(var['name'])
|
||||||
if var['type'] in structs:
|
|
||||||
completer = QCompleter(structs[var['type']].keys())
|
|
||||||
completer.setCaseSensitivity(Qt.CaseInsensitive)
|
|
||||||
name_edit.setCompleter(completer)
|
|
||||||
name_edit.textChanged.connect(on_change_callback)
|
name_edit.textChanged.connect(on_change_callback)
|
||||||
name_edit.setStyleSheet(style_with_padding)
|
name_edit.setStyleSheet(style_with_padding)
|
||||||
self.setCellWidget(row, rows.name, name_edit)
|
self.setCellWidget(row, rows.name, name_edit)
|
||||||
|
|
||||||
# Origin Type (readonly)
|
# Origin Type
|
||||||
origin_item = QTableWidgetItem(var.get('type', ''))
|
origin_item = QTableWidgetItem(var.get('type', ''))
|
||||||
origin_item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
|
origin_item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
|
||||||
origin_item.setToolTip(var.get('type', '')) # Всплывающая подсказка
|
origin_item.setToolTip(var.get('type', ''))
|
||||||
origin_item.setForeground(QBrush(Qt.black))
|
origin_item.setForeground(QBrush(Qt.black))
|
||||||
self.setItem(row, rows.type, origin_item)
|
self.setItem(row, rows.type, origin_item)
|
||||||
|
|
||||||
# pt_type
|
# pt_type
|
||||||
pt_combo = CtrlScrollComboBox()
|
pt_combo = CtrlScrollComboBox()
|
||||||
pt_combo.addItems(self.pt_types)
|
pt_combo.addItems(self.pt_types)
|
||||||
value = var['pt_type'].replace('pt_', '')
|
value = var.get('pt_type', 'unknown').replace('pt_', '')
|
||||||
if value not in self.pt_types:
|
if value not in self.pt_types:
|
||||||
pt_combo.addItem(value)
|
pt_combo.addItem(value)
|
||||||
pt_combo.setCurrentText(value)
|
pt_combo.setCurrentText(value)
|
||||||
@ -185,7 +242,7 @@ class VariableTableWidget(QTableWidget):
|
|||||||
# iq_type
|
# iq_type
|
||||||
iq_combo = CtrlScrollComboBox()
|
iq_combo = CtrlScrollComboBox()
|
||||||
iq_combo.addItems(self.iq_types)
|
iq_combo.addItems(self.iq_types)
|
||||||
value = var['iq_type'].replace('t_', '')
|
value = var.get('iq_type', 'iq_none').replace('t_', '')
|
||||||
if value not in self.iq_types:
|
if value not in self.iq_types:
|
||||||
iq_combo.addItem(value)
|
iq_combo.addItem(value)
|
||||||
iq_combo.setCurrentText(value)
|
iq_combo.setCurrentText(value)
|
||||||
@ -196,19 +253,32 @@ class VariableTableWidget(QTableWidget):
|
|||||||
# return_type
|
# return_type
|
||||||
ret_combo = CtrlScrollComboBox()
|
ret_combo = CtrlScrollComboBox()
|
||||||
ret_combo.addItems(self.iq_types)
|
ret_combo.addItems(self.iq_types)
|
||||||
value = var['return_type'].replace('t_', '')
|
value = var.get('return_type', 'iq_none').replace('t_', '')
|
||||||
|
if value not in self.iq_types:
|
||||||
|
ret_combo.addItem(value)
|
||||||
ret_combo.setCurrentText(value)
|
ret_combo.setCurrentText(value)
|
||||||
ret_combo.currentTextChanged.connect(on_change_callback)
|
ret_combo.currentTextChanged.connect(on_change_callback)
|
||||||
ret_combo.setStyleSheet(style_with_padding)
|
ret_combo.setStyleSheet(style_with_padding)
|
||||||
self.setCellWidget(row, rows.ret_type, ret_combo)
|
self.setCellWidget(row, rows.ret_type, ret_combo)
|
||||||
|
|
||||||
# short_name
|
# Последний столбец
|
||||||
short_name_val = var.get('shortname', var['name'])
|
if self._show_value:
|
||||||
short_name_edit = QLineEdit(short_name_val)
|
val = var.get('value', '')
|
||||||
short_name_edit.textChanged.connect(on_change_callback)
|
if val is None:
|
||||||
short_name_edit.setStyleSheet(style_with_padding)
|
val = ''
|
||||||
self.setCellWidget(row, rows.short_name, short_name_edit)
|
val_edit = QLineEdit(str(val))
|
||||||
|
val_edit.textChanged.connect(on_change_callback)
|
||||||
|
val_edit.setStyleSheet(style_with_padding)
|
||||||
|
self.setCellWidget(row, rows.short_name, val_edit)
|
||||||
|
else:
|
||||||
|
short_name_val = var.get('shortname', var['name'])
|
||||||
|
short_name_edit = QLineEdit(short_name_val)
|
||||||
|
short_name_edit.textChanged.connect(on_change_callback)
|
||||||
|
short_name_edit.setStyleSheet(style_with_padding)
|
||||||
|
self.setCellWidget(row, rows.short_name, short_name_edit)
|
||||||
|
|
||||||
|
self.blockSignals(False)
|
||||||
|
self.setUpdatesEnabled(True)
|
||||||
self.check()
|
self.check()
|
||||||
|
|
||||||
def check(self):
|
def check(self):
|
||||||
@ -231,7 +301,7 @@ class VariableTableWidget(QTableWidget):
|
|||||||
t3 = time.time()
|
t3 = time.time()
|
||||||
shortname = short_name_edit.text() if short_name_edit else ""
|
shortname = short_name_edit.text() if short_name_edit else ""
|
||||||
|
|
||||||
long_shortname = len(shortname) > 10
|
long_shortname = len(shortname) > self._shortname_size
|
||||||
found = name in var_names_set
|
found = name in var_names_set
|
||||||
|
|
||||||
color = None
|
color = None
|
||||||
@ -293,6 +363,13 @@ class VariableTableWidget(QTableWidget):
|
|||||||
if dlg.exec_():
|
if dlg.exec_():
|
||||||
self._ret_type_filter = dlg.get_selected()
|
self._ret_type_filter = dlg.get_selected()
|
||||||
self.update_comboboxes({rows.ret_type: self._ret_type_filter})
|
self.update_comboboxes({rows.ret_type: self._ret_type_filter})
|
||||||
|
|
||||||
|
elif logicalIndex == rows.short_name:
|
||||||
|
dlg = SetSizeDialog(self)
|
||||||
|
if dlg.exec_():
|
||||||
|
self._shortname_size = dlg.get_selected_size()
|
||||||
|
self.settings.setValue("shortname_size", self._shortname_size)
|
||||||
|
self.check()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -312,10 +389,9 @@ class VariableTableWidget(QTableWidget):
|
|||||||
combo.clear()
|
combo.clear()
|
||||||
|
|
||||||
combo.addItems(allowed_items)
|
combo.addItems(allowed_items)
|
||||||
if current in allowed_items:
|
if current not in allowed_items:
|
||||||
combo.setCurrentText(current)
|
combo.addItem(current)
|
||||||
else:
|
combo.setCurrentText(current)
|
||||||
combo.setCurrentIndex(0)
|
|
||||||
combo.blockSignals(False)
|
combo.blockSignals(False)
|
||||||
|
|
||||||
|
|
||||||
|
648
debug_tools.c
648
debug_tools.c
@ -1,134 +1,288 @@
|
|||||||
#include "debug_tools.h"
|
#include "debug_tools.h"
|
||||||
#include "IQmathLib.h"
|
|
||||||
|
|
||||||
DebugLowLevel_t debug_ll = DEBUG_LOWLEVEL_INIT;
|
#if !defined(GLOBAL_Q)
|
||||||
|
#define GLOBAL_Q 16
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
DebugLowLevel_t debug_ll = DEBUG_LOWLEVEL_INIT; ///< Ñòðóêòóðà îòëàäêè íèæíåãî óðîâíÿ (èíèöèàëèçàöèÿ)
|
||||||
|
|
||||||
|
static int getDebugVar(DebugVar_t *var, int32_t *int_var, float *float_var);
|
||||||
|
static int convertDebugVarToIQx(DebugVar_t *var, int32_t *ret_var);
|
||||||
|
static int iqTypeToQ(DebugVarIQType_t t);
|
||||||
|
static int is_addr_in_allowed_ranges(uint32_t addr_val, const AddrRange_t *ranges, int count);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Ìàññèâ äîïóñòèìûõ äèàïàçîíîâ àäðåñîâ äëÿ îòëàäî÷íîãî ÷òåíèÿ
|
||||||
|
*
|
||||||
|
* Âêëþ÷àåò â ñåáÿ íàáîð äèàïàçîíîâ ïàìÿòè, ðàçðåø¸ííûõ äëÿ äîñòóïà
|
||||||
|
* ôóíêöèåé Debug_LowLevel_ReadVar.
|
||||||
|
*/
|
||||||
|
static const AddrRange_t debug_allowed_ranges[] = ALLOWED_ADDRESS_RANGES;
|
||||||
|
/**
|
||||||
|
* @brief Êîëè÷åñòâî ýëåìåíòîâ â ìàññèâå debug_allowed_ranges
|
||||||
|
*/
|
||||||
|
static const int debug_allowed_ranges_count = sizeof(debug_allowed_ranges) / sizeof(debug_allowed_ranges[0]);
|
||||||
|
|
||||||
static int getDebugVar(DebugVar_t *var, long *int_var, float *float_var);
|
|
||||||
static int convertDebugVarToIQx(DebugVar_t *var, long *ret_var);
|
|
||||||
|
|
||||||
///////////////////////////----EXAPLE-----//////////////////////////////
|
///////////////////////////----EXAPLE-----//////////////////////////////
|
||||||
long var_numb = 1;
|
int var_numb = 1; ///< Ïðèìåð ïåðåìåííîé äëÿ îòëàäêè
|
||||||
DebugVarName_t var_name;
|
DebugVarName_t var_name; ///< Èìÿ ïåðåìåííîé
|
||||||
long return_var;
|
int32_t return_var; ///< Ïåðåìåííàÿ äëÿ âîçâðàòà ðåçóëüòàòà
|
||||||
long return_ll_var;
|
int32_t return_ll_var; ///< Âîçâðàùàåìîå çíà÷åíèå ñ íèæíåãî óðîâíÿ
|
||||||
int result;
|
int result; ///< Ïåðåìåííàÿ ðåçóëüòàòà
|
||||||
char ext_date[] = {7, 233, 11, 07, 16, 50};
|
DateTime_t ext_date = {2025, 11, 07, 16, 50}; ///< Ïðèìåð âíåøíåé äàòû ñáîðêè
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Ïðèìåð èñïîëüçîâàíèÿ ôóíêöèé îòëàäêè.
|
||||||
|
* @details Äåìîíñòðàöèîííàÿ ôóíêöèÿ äëÿ ðàáîòû ñ ïåðåìåííûìè îòëàäêè.
|
||||||
|
*/
|
||||||
void Debug_Test_Example(void)
|
void Debug_Test_Example(void)
|
||||||
{
|
{
|
||||||
|
return;
|
||||||
result = Debug_ReadVar(var_numb, &return_var);
|
result = Debug_ReadVar(var_numb, &return_var);
|
||||||
result = Debug_ReadVarName(var_numb, var_name);
|
result = Debug_ReadVarName(var_numb, var_name, 0);
|
||||||
|
|
||||||
|
|
||||||
if(Debug_LowLevel_Initialize(ext_date) == 0)
|
if(Debug_LowLevel_Initialize(&ext_date) == 0)
|
||||||
result = Debug_LowLevel_ReadVar(&return_ll_var);
|
result = Debug_LowLevel_ReadVar(&return_ll_var);
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////----PUBLIC-----//////////////////////////////
|
///////////////////////////----PUBLIC-----//////////////////////////////
|
||||||
int Debug_ReadVar(int var_ind, long *return_long)
|
|
||||||
|
/**
|
||||||
|
* @brief ×èòàåò ïåðåìåííóþ ïî èíäåêñó.
|
||||||
|
* @param var_ind – èíäåêñ ïåðåìåííîé.
|
||||||
|
* @param return_32b – óêàçàòåëü äëÿ âîçâðàòà ðåçóëüòàòà.
|
||||||
|
* @return int – 0: óñïåõ, 1: îøèáêà.
|
||||||
|
* @details Èñïîëüçóåòñÿ äëÿ ÷òåíèÿ çíà÷åíèé ïåðåìåííûõ ïî èõ èíäåêñó.
|
||||||
|
*/
|
||||||
|
int Debug_ReadVar(int var_ind, int32_t *return_32b)
|
||||||
{
|
{
|
||||||
if(return_long == NULL)
|
int32_t tmp_var;
|
||||||
return 1;
|
|
||||||
long tmp_var;
|
|
||||||
|
|
||||||
|
if(return_32b == NULL)
|
||||||
|
return DEBUG_ERR_INTERNAL;
|
||||||
if (var_ind >= DebugVar_Qnt)
|
if (var_ind >= DebugVar_Qnt)
|
||||||
return 1;
|
return DEBUG_ERR_VAR_NUMB;
|
||||||
if((dbg_vars[var_numb].ptr_type == pt_struct) || (dbg_vars[var_numb].ptr_type == pt_union) ||
|
if((dbg_vars[var_ind].ptr_type == pt_struct) || (dbg_vars[var_ind].ptr_type == pt_union) ||
|
||||||
(dbg_vars[var_numb].ptr_type == pt_unknown))
|
(dbg_vars[var_ind].ptr_type == pt_unknown))
|
||||||
return 1;
|
return DEBUG_ERR_INVALID_VAR;
|
||||||
|
|
||||||
|
return convertDebugVarToIQx(&dbg_vars[var_ind], return_32b);
|
||||||
|
|
||||||
return convertDebugVarToIQx(&dbg_vars[var_numb], return_long);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int Debug_ReadVarName(int var_ind, DebugVarName_t name_ptr)
|
/**
|
||||||
|
* @brief ×èòàåò âîçâðàùàåìûé òèï (IQ) ïåðåìåííîé ïî èíäåêñó.
|
||||||
|
* @param var_ind – èíäåêñ ïåðåìåííîé.
|
||||||
|
* @param vartype – óêàçàòåëü äëÿ âîçâðàòà òèïà.
|
||||||
|
* @return int – 0: óñïåõ, 1: îøèáêà.
|
||||||
|
* @details Èñïîëüçóåòñÿ äëÿ ÷òåíèÿ âîçâðàùàåìîãî òèïà (IQ) ïåðåìåííûõ ïî èõ èíäåêñó.
|
||||||
|
*/
|
||||||
|
int Debug_ReadVarReturnType(int var_ind, int *vartype)
|
||||||
{
|
{
|
||||||
if(name_ptr == NULL)
|
int rettype;
|
||||||
return 1;
|
if(vartype == NULL)
|
||||||
|
return DEBUG_ERR_INTERNAL;
|
||||||
if (var_ind >= DebugVar_Qnt)
|
if (var_ind >= DebugVar_Qnt)
|
||||||
return 1;
|
return DEBUG_ERR_VAR_NUMB;
|
||||||
|
if((dbg_vars[var_ind].ptr_type == pt_struct) || (dbg_vars[var_ind].ptr_type == pt_union) ||
|
||||||
|
(dbg_vars[var_ind].ptr_type == pt_unknown))
|
||||||
|
return DEBUG_ERR_INVALID_VAR;
|
||||||
|
|
||||||
int i;
|
*vartype = iqTypeToQ(dbg_vars[var_ind].return_type);
|
||||||
// Êîïèðîâàíèå ñ çàùèòîé îò ïåðåïîëíåíèÿ è ÿâíîé îñòàíîâêîé ïî '\0'
|
|
||||||
for (i = 0; i < sizeof(dbg_vars[var_numb].name); i++)
|
|
||||||
{
|
|
||||||
name_ptr[i] = dbg_vars[var_numb].name[i];
|
|
||||||
if (dbg_vars[var_numb].name[i] == '\0')
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// Ãàðàíòèðîâàííîå çàâåðøåíèå ñòðîêè (íà ñëó÷àé, åñëè â var->name íå áûëî '\0')
|
|
||||||
name_ptr[sizeof(dbg_vars[var_numb].name) - 1] = '\0';
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief ×èòàåò òèï ïåðåìåííîé ïî èíäåêñó.
|
||||||
int Debug_LowLevel_ReadVar(long *return_long)
|
* @param var_ind – èíäåêñ ïåðåìåííîé.
|
||||||
|
* @param vartype – óêàçàòåëü äëÿ âîçâðàòà òèïà.
|
||||||
|
* @return int – 0: óñïåõ, 1: îøèáêà.
|
||||||
|
* @details Èñïîëüçóåòñÿ äëÿ ÷òåíèÿ òèïà ïåðåìåííûõ ïî èõ èíäåêñó.
|
||||||
|
*/
|
||||||
|
int Debug_ReadVarType(int var_ind, int *vartype)
|
||||||
{
|
{
|
||||||
if (return_long == NULL)
|
int rettype;
|
||||||
return 1;
|
if(vartype == NULL)
|
||||||
if (debug_ll.isVerified == 0)
|
return DEBUG_ERR_INTERNAL;
|
||||||
return 1;
|
if (var_ind >= DebugVar_Qnt)
|
||||||
|
return DEBUG_ERR_VAR_NUMB;
|
||||||
|
if((dbg_vars[var_ind].ptr_type == pt_struct) || (dbg_vars[var_ind].ptr_type == pt_union) ||
|
||||||
|
(dbg_vars[var_ind].ptr_type == pt_unknown))
|
||||||
|
return DEBUG_ERR_INVALID_VAR;
|
||||||
|
|
||||||
char *addr = debug_ll.dbg_var.Ptr;
|
*vartype = dbg_vars[var_ind].ptr_type;
|
||||||
unsigned long addr_val = (unsigned long)addr;
|
|
||||||
|
|
||||||
// Ðàçðåø¸ííûå äèàïàçîíû ïàìÿòè (èç .cmd ôàéëà)
|
switch(dbg_vars[var_ind].ptr_type)
|
||||||
if (!(
|
{
|
||||||
(addr_val <= 0x0007FF) || // RAMM0 + RAMM1
|
case pt_int8:
|
||||||
(addr_val >= 0x008120 && addr_val <= 0x009FFC) || // L0 + L1 SARAM
|
case pt_int16:
|
||||||
(addr_val >= 0x3F8000 && addr_val <= 0x3F9FFF) || // PRAMH0 + DRAMH0
|
case pt_int32:
|
||||||
(addr_val >= 0x3FF000 && addr_val <= 0x3FFFFF) || // BOOTROM + RESET
|
case pt_float:
|
||||||
(addr_val >= 0x080002 && addr_val <= 0x09FFFF) || // RAMEX1
|
*vartype = dbg_vars[var_ind].ptr_type | DEBUG_SIGNED_VAR;
|
||||||
(addr_val >= 0x0F0000 && addr_val <= 0x0FFEFF) || // RAMEX4
|
break;
|
||||||
(addr_val >= 0x100002 && addr_val <= 0x103FFF) || // RAMEX0 + RAMEX2 + RAMEX01
|
|
||||||
(addr_val >= 0x102000 && addr_val <= 0x103FFF) // RAMEX2
|
default:
|
||||||
)) {
|
*vartype = dbg_vars[var_ind].ptr_type;
|
||||||
return 2; // Çàïðåù¸ííûé àäðåñ — íåëüçÿ ÷èòàòü
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return convertDebugVarToIQx(&debug_ll.dbg_var, return_long);
|
return 0;
|
||||||
;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
int Debug_LowLevel_Initialize(const char* external_date)
|
* @brief ×èòàåò èìÿ ïåðåìåííîé ïî èíäåêñó.
|
||||||
|
* @param var_ind – èíäåêñ ïåðåìåííîé.
|
||||||
|
* @param name_ptr – óêàçàòåëü íà áóôåð èìåíè (DebugVarName_t).
|
||||||
|
* @return int – 0: óñïåõ, 1: îøèáêà.
|
||||||
|
* @details Êîïèðóåò èìÿ ïåðåìåííîé â ïðåäîñòàâëåííûé áóôåð.
|
||||||
|
*/
|
||||||
|
int Debug_ReadVarName(int var_ind, DebugVarName_t name_ptr, int *length)
|
||||||
{
|
{
|
||||||
if (external_date == NULL) {
|
int i;
|
||||||
return -1;
|
|
||||||
|
if(name_ptr == NULL)
|
||||||
|
return DEBUG_ERR_INTERNAL;
|
||||||
|
|
||||||
|
if (var_ind >= DebugVar_Qnt)
|
||||||
|
return DEBUG_ERR_VAR_NUMB;
|
||||||
|
|
||||||
|
// Êîïèðîâàíèå ñ çàùèòîé îò ïåðåïîëíåíèÿ è ÿâíîé îñòàíîâêîé ïî '\0'
|
||||||
|
for (i = 0; i < sizeof(dbg_vars[var_ind].name); i++)
|
||||||
|
{
|
||||||
|
name_ptr[i] = dbg_vars[var_ind].name[i];
|
||||||
|
if (dbg_vars[var_ind].name[i] == '\0')
|
||||||
|
{
|
||||||
|
if(length != NULL)
|
||||||
|
*length = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Ãàðàíòèðîâàííîå çàâåðøåíèå ñòðîêè (íà ñëó÷àé, åñëè â var->name íå áûëî '\0')
|
||||||
|
name_ptr[sizeof(dbg_vars[var_ind].name) - 1] = '\0';
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief ×èòàåò çíà÷åíèå ïåðåìåííîé îòëàäêè ñ íèæíåãî óðîâíÿ.
|
||||||
|
* @param return_32b – óêàçàòåëü íà ïåðåìåííóþ, êóäà çàïèñûâàåòñÿ ðåçóëüòàò.
|
||||||
|
* @return int – 0: óñïåõ, 1: îøèáêà, 2: íåäîïóñòèìûé àäðåñ.
|
||||||
|
* @details Èñïîëüçóåò àäðåññ, ïåðåäàâàåìûé ñ òåðìèíàëêè äëÿ ïîëó÷åíèÿ çíà÷åíèÿ.
|
||||||
|
*/
|
||||||
|
int Debug_LowLevel_ReadVar(int32_t *return_32b)
|
||||||
|
{
|
||||||
|
uint8_t *addr = debug_ll.dbg_var.Ptr;
|
||||||
|
uint32_t addr_val = (uint32_t)addr;
|
||||||
|
|
||||||
|
if (return_32b == NULL)
|
||||||
|
return DEBUG_ERR_INTERNAL;
|
||||||
|
if (debug_ll.isVerified == 0)
|
||||||
|
return DEBUG_ERR_DATATIME;
|
||||||
|
|
||||||
|
if (is_addr_in_allowed_ranges(addr_val, debug_allowed_ranges, debug_allowed_ranges_count) != 0)
|
||||||
|
{
|
||||||
|
return DEBUG_ERR_ADDR; // Çàïðåù¸ííûé àäðåñ — íåëüçÿ ÷èòàòü
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ïðåîáðàçóåì external_date â ñòðóêòóðó
|
return convertDebugVarToIQx(&debug_ll.dbg_var, return_32b);
|
||||||
DateTime_t ext;
|
}
|
||||||
ext.year = (external_date[0] << 8) | external_date[1];
|
|
||||||
ext.day = external_date[2];
|
/**
|
||||||
ext.month = external_date[3];
|
* @brief Èíèöèàëèçàöèÿ îòëàäêè íà íèæíåì óðîâíå ïî äàòå ñáîðêè.
|
||||||
ext.hour = external_date[4];
|
* @param external_date – ñòðóêòóðà ñ äàòîé DateTime_t
|
||||||
ext.minute = external_date[5];
|
* @return int – 0: ñîâïàäàåò, 1: íå ñîâïàäàåò, -1: îøèáêà.
|
||||||
|
* @details Ñðàâíèâàåò äàòó êîìïèëÿöèè ñ çàïðàøèâàåìîé è èíèöèàëèçèðóåò îòëàäî÷íóþ ïåðåìåííóþ.
|
||||||
|
*/
|
||||||
|
int Debug_LowLevel_Initialize(DateTime_t* external_date)
|
||||||
|
{
|
||||||
|
if (external_date == NULL) {
|
||||||
|
return DEBUG_ERR_INTERNAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Ñðàâíåíèå âñåõ ïîëåé
|
// Ñðàâíåíèå âñåõ ïîëåé
|
||||||
if (ext.year == debug_ll.build_date.year &&
|
if (external_date->year == debug_ll.build_date.year &&
|
||||||
ext.month == debug_ll.build_date.month &&
|
external_date->month == debug_ll.build_date.month &&
|
||||||
ext.day == debug_ll.build_date.day &&
|
external_date->day == debug_ll.build_date.day &&
|
||||||
ext.hour == debug_ll.build_date.hour &&
|
external_date->hour == debug_ll.build_date.hour &&
|
||||||
ext.minute == debug_ll.build_date.minute)
|
external_date->minute == debug_ll.build_date.minute)
|
||||||
{
|
{
|
||||||
debug_ll.isVerified = 1;
|
debug_ll.isVerified = 1;
|
||||||
return 0; // Ñîâïàëî
|
return 0; // Ñîâïàëî
|
||||||
}
|
}
|
||||||
debug_ll.isVerified = 0;
|
debug_ll.isVerified = 0;
|
||||||
|
|
||||||
return 1; // Íå ñîâïàëî
|
return DEBUG_ERR_DATATIME; // Íå ñîâïàëî
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief ×èòàåò âîçâðàùàåìûé òèï (IQ) íèçêîóðîâíåíî çàäàííîé ïåðåìåííîé.
|
||||||
|
* @param var_ind – èíäåêñ ïåðåìåííîé.
|
||||||
|
* @param vartype – óêàçàòåëü äëÿ âîçâðàòà òèïà.
|
||||||
|
* @return int – 0: óñïåõ, 1: îøèáêà.
|
||||||
|
* @details Èñïîëüçóåòñÿ äëÿ ÷òåíèÿ âîçâðàùàåìîãî òèïà (IQ) ïåðåìåííûõ ïî èõ èíäåêñó.
|
||||||
|
*/
|
||||||
|
int Debug_LowLevel_ReadVarReturnType(int *vartype)
|
||||||
|
{
|
||||||
|
int rettype;
|
||||||
|
if(vartype == NULL)
|
||||||
|
return DEBUG_ERR_INTERNAL;
|
||||||
|
if((debug_ll.dbg_var.ptr_type == pt_struct) || (debug_ll.dbg_var.ptr_type == pt_union) ||
|
||||||
|
(debug_ll.dbg_var.ptr_type == pt_unknown))
|
||||||
|
return DEBUG_ERR_INVALID_VAR;
|
||||||
|
|
||||||
|
*vartype = iqTypeToQ(debug_ll.dbg_var.return_type);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief ×èòàåò òèï íèçêîóðîâíåíî çàäàííîé ïåðåìåííîé.
|
||||||
|
* @param var_ind – èíäåêñ ïåðåìåííîé.
|
||||||
|
* @param vartype – óêàçàòåëü äëÿ âîçâðàòà òèïà.
|
||||||
|
* @return int – 0: óñïåõ, 1: îøèáêà.
|
||||||
|
*/
|
||||||
|
int Debug_LowLevel_ReadVarType(int *vartype)
|
||||||
|
{
|
||||||
|
int rettype;
|
||||||
|
if(vartype == NULL)
|
||||||
|
return DEBUG_ERR_INTERNAL;
|
||||||
|
if((debug_ll.dbg_var.ptr_type == pt_struct) || (debug_ll.dbg_var.ptr_type == pt_union) ||
|
||||||
|
(debug_ll.dbg_var.ptr_type == pt_unknown))
|
||||||
|
return DEBUG_ERR_INVALID_VAR;
|
||||||
|
|
||||||
|
*vartype = debug_ll.dbg_var.ptr_type;
|
||||||
|
|
||||||
|
switch(debug_ll.dbg_var.ptr_type)
|
||||||
|
{
|
||||||
|
case pt_int8:
|
||||||
|
case pt_int16:
|
||||||
|
case pt_int32:
|
||||||
|
case pt_float:
|
||||||
|
*vartype = debug_ll.dbg_var.ptr_type | DEBUG_SIGNED_VAR;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
*vartype = debug_ll.dbg_var.ptr_type;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/////////////////////----INTERNAL FUNCTIONS-----////////////////////////
|
/////////////////////----INTERNAL FUNCTIONS-----////////////////////////
|
||||||
|
/**
|
||||||
|
* @brief Ïðåîáðàçóåò òèï IQ ïåðåìåííîé â ÷èñëî áèòîâ äëÿ ñäâèãà(Q-ôàêòîð).
|
||||||
|
* @param t – òèï IQ (ïåðå÷èñëåíèå DebugVarIQType_t).
|
||||||
|
* @return int – Q-ôàêòîð (íàïðèìåð, 24), 0: åñëè t_iq_none, -1: îøèáêà.
|
||||||
|
* @details Ñîïîñòàâëÿåò òèï IQ ïåðåìåííîé ñ ñîîòâåòñòâóþùèì Q-çíà÷åíèåì.
|
||||||
|
*/
|
||||||
static int iqTypeToQ(DebugVarIQType_t t)
|
static int iqTypeToQ(DebugVarIQType_t t)
|
||||||
{
|
{
|
||||||
if (t == t_iq_none)
|
if (t == t_iq_none)
|
||||||
@ -138,47 +292,53 @@ static int iqTypeToQ(DebugVarIQType_t t)
|
|||||||
else if (t >= t_iq1 && t <= t_iq30)
|
else if (t >= t_iq1 && t <= t_iq30)
|
||||||
return (int)t - (int)t_iq1 + 1; // íàïðèìåð t_iq1 -> 1, t_iq2 -> 2 è ò.ä.
|
return (int)t - (int)t_iq1 + 1; // íàïðèìåð t_iq1 -> 1, t_iq2 -> 2 è ò.ä.
|
||||||
else
|
else
|
||||||
return -1; // îøèáêà
|
return 0; // îøèáêà
|
||||||
}
|
}
|
||||||
|
|
||||||
static int convertDebugVarToIQx(DebugVar_t *var, long *ret_var)
|
/**
|
||||||
|
* @brief Ïðåîáðàçóåò ïåðåìåííóþ îòëàäêè â IQ ôîðìàò.
|
||||||
|
* @param var – óêàçàòåëü íà ïåðåìåííóþ îòëàäêè.
|
||||||
|
* @param ret_var – óêàçàòåëü äëÿ âîçâðàòà çíà÷åíèÿ â ôîðìàòå 32 áèòà.
|
||||||
|
* @return int – 0: óñïåõ, 1: îøèáêà ÷òåíèÿ, 2: íåïðàâèëüíûé ôîðìàò, 3: ïåðåïîëíåíèå.
|
||||||
|
* @details Îïðåäåëÿåò ôîðìàò IQ ïåðåìåííîé, êîíâåðòèðóåò å¸ â 32b ñ ó÷¸òîì ìàñøòàáà.
|
||||||
|
*/
|
||||||
|
static int convertDebugVarToIQx(DebugVar_t *var, int32_t *ret_var)
|
||||||
{
|
{
|
||||||
long iq_numb, iq_united, iq_final;
|
int32_t iq_numb, iq_united, iq_final;
|
||||||
|
int64_t iq_united64 = 0;
|
||||||
|
int64_t iq_final64 = 0;
|
||||||
|
int status;
|
||||||
|
|
||||||
float float_numb;
|
float float_numb;
|
||||||
|
|
||||||
if(getDebugVar(var, &iq_numb, &float_numb) != 0)
|
status = getDebugVar(var, &iq_numb, &float_numb);
|
||||||
return 1;
|
if(status != 0)
|
||||||
|
return status;
|
||||||
|
|
||||||
int src_q = iqTypeToQ(var->iq_type);
|
int src_q = iqTypeToQ(var->iq_type);
|
||||||
int dst_q = iqTypeToQ(var->return_type);
|
int dst_q = iqTypeToQ(var->return_type);
|
||||||
|
|
||||||
if (src_q < 0 || dst_q < 0)
|
|
||||||
return 2; // íåïðàâèëüíûé ôîðìàò
|
|
||||||
|
|
||||||
long long iq_united64 = 0;
|
|
||||||
long long iq_final64 = 0;
|
|
||||||
|
|
||||||
// Êîíâåðòàöèÿ ê GLOBAL_Q (64-áèò)
|
// Êîíâåðòàöèÿ ê GLOBAL_Q (64-áèò)
|
||||||
if (var->iq_type == t_iq_none) {
|
if (var->iq_type == t_iq_none) {
|
||||||
if (var->ptr_type == pt_float) {
|
if (var->ptr_type == pt_float) {
|
||||||
// float_numb óìíîæàåì íà 2^GLOBAL_Q (2^24=16777216)
|
// float_numb óìíîæàåì íà 2^GLOBAL_Q
|
||||||
// Ðåçóëüòàò ïðèâîäèì ê long long
|
// Ðåçóëüòàò ïðèâîäèì ê 64 áèòà
|
||||||
iq_united64 = (long long)(float_numb * 16777216.0f);
|
iq_united64 = (int64_t)(float_numb * ((uint32_t)1 << GLOBAL_Q));
|
||||||
} else {
|
} else {
|
||||||
iq_united64 = ((long long)iq_numb) << GLOBAL_Q;
|
iq_united64 = ((int64_t)iq_numb) << GLOBAL_Q;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
int shift = GLOBAL_Q - src_q;
|
int shift = GLOBAL_Q - src_q;
|
||||||
if (shift >= 0)
|
if (shift >= 0)
|
||||||
iq_united64 = ((long long)iq_numb) << shift;
|
iq_united64 = ((int64_t)iq_numb) << shift;
|
||||||
else
|
else
|
||||||
iq_united64 = ((long long)iq_numb) >> (-shift);
|
iq_united64 = ((int64_t)iq_numb) >> (-shift);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Êîíâåðòàöèÿ èç GLOBAL_Q â öåëåâîé IQ (64-áèò)
|
// Êîíâåðòàöèÿ èç GLOBAL_Q â öåëåâîé IQ (64-áèò)
|
||||||
if (var->return_type == t_iq_none) {
|
if (var->return_type == t_iq_none) {
|
||||||
// Âîçâðàùàåì öåëîå, îòáðîñèâ äðîáíóþ ÷àñòü
|
// Âîçâðàùàåì öåëîå, îòáðîñèâ äðîáíóþ ÷àñòü
|
||||||
*ret_var = (long)(iq_united64 >> GLOBAL_Q);
|
*ret_var = (uint32_t)(iq_united64 >> GLOBAL_Q);
|
||||||
} else {
|
} else {
|
||||||
int shift = dst_q - GLOBAL_Q;
|
int shift = dst_q - GLOBAL_Q;
|
||||||
if (shift >= 0)
|
if (shift >= 0)
|
||||||
@ -186,61 +346,71 @@ static int convertDebugVarToIQx(DebugVar_t *var, long *ret_var)
|
|||||||
else
|
else
|
||||||
iq_final64 = iq_united64 >> (-shift);
|
iq_final64 = iq_united64 >> (-shift);
|
||||||
|
|
||||||
// Ïðîâåðÿåì ïåðåïîëíåíèå int32_t
|
*ret_var = (int32_t)iq_final64;
|
||||||
if (iq_final64 > LONG_MAX || iq_final64 < LONG_MIN)
|
|
||||||
return 3; // ïåðåïîëíåíèå
|
|
||||||
|
|
||||||
*ret_var = (long)iq_final64;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int getDebugVar(DebugVar_t *var, long *int_var, float *float_var)
|
/**
|
||||||
|
* @brief Ïðî÷èòàòü çíà÷åíèå ïåðåìåííîé îòëàäêè.
|
||||||
|
* @param var – óêàçàòåëü íà ñòðóêòóðó DebugVar.
|
||||||
|
* @param int_var – óêàçàòåëü íà ïåðåìåííóþ òèïà 32 áèòà äëÿ âîçâðàòà öåëî÷èñëåííîãî çíà÷åíèÿ.
|
||||||
|
* @param float_var – óêàçàòåëü íà ïåðåìåííóþ òèïà float äëÿ âîçâðàòà çíà÷åíèÿ ñ ïëàâàþùåé òî÷êîé.
|
||||||
|
* @return int – 0: óñïåõ, 1: îøèáêà óêàçàòåëåé èëè íåïîääåðæèâàåìûé òèï, 3/4: îøèáêà âûðàâíèâàíèÿ.
|
||||||
|
* @details  çàâèñèìîñòè îò òèïà ïåðåìåííîé ñ÷èòûâàåò å¸ çíà÷åíèå è ñîõðàíÿåò â ñîîòâåòñòâóþùåì óêàçàòåëå.
|
||||||
|
*/
|
||||||
|
static int getDebugVar(DebugVar_t *var, int32_t *int_var, float *float_var)
|
||||||
{
|
{
|
||||||
if (!var || !int_var || !float_var || !var->Ptr)
|
uint8_t *addr = var->Ptr;
|
||||||
return 1; // îøèáêà: null óêàçàòåëü
|
uint32_t addr_val = (uint32_t)addr;
|
||||||
|
|
||||||
char *addr = var->Ptr;
|
if (!var || !int_var || !float_var || !var->Ptr)
|
||||||
unsigned long addr_val = (unsigned long)addr;
|
return DEBUG_ERR_INTERNAL; // îøèáêà: null óêàçàòåëü
|
||||||
|
|
||||||
switch (var->ptr_type)
|
switch (var->ptr_type)
|
||||||
{
|
{
|
||||||
case pt_int8: // 8 áèò
|
case pt_int8: // 8 áèò
|
||||||
|
if ((addr_val & ALIGN_8BIT) != 0) // ïðîâåðÿåì âûðàâíèâàíèå
|
||||||
|
return DEBUG_ERR_ADDR_ALIGN; // îøèáêà âûðàâíèâàíèÿ
|
||||||
|
*int_var = *((volatile int8_t *)addr);
|
||||||
|
break;
|
||||||
case pt_uint8:
|
case pt_uint8:
|
||||||
// âûðàâíèâàíèå íå íóæíî äëÿ 8 áèò
|
if ((addr_val & ALIGN_8BIT) != 0) // ïðîâåðÿåì âûðàâíèâàíèå
|
||||||
*int_var = *((volatile char *)addr);
|
return DEBUG_ERR_ADDR_ALIGN; // îøèáêà âûðàâíèâàíèÿ
|
||||||
|
*int_var = *((volatile uint8_t *)addr);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case pt_int16: // 16 áèò (int)
|
case pt_int16: // 16 áèò (int)
|
||||||
|
if ((addr_val & ALIGN_16BIT) != 0) // ïðîâåðÿåì âûðàâíèâàíèå
|
||||||
|
return DEBUG_ERR_ADDR_ALIGN; // îøèáêà âûðàâíèâàíèÿ
|
||||||
|
*int_var = *((volatile int16_t *)addr);
|
||||||
|
break;
|
||||||
case pt_uint16:
|
case pt_uint16:
|
||||||
*int_var = *((volatile int *)addr);
|
if ((addr_val & ALIGN_16BIT) != 0) // ïðîâåðÿåì âûðàâíèâàíèå
|
||||||
|
return DEBUG_ERR_ADDR_ALIGN; // îøèáêà âûðàâíèâàíèÿ
|
||||||
|
*int_var = *((volatile uint16_t *)addr);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case pt_int32: // 32 áèò (long)
|
case pt_int32: // 32 áèò
|
||||||
|
if ((addr_val & ALIGN_32BIT) != 0) // ïðîâåðÿåì âûðàâíèâàíèå
|
||||||
|
return DEBUG_ERR_ADDR_ALIGN; // îøèáêà âûðàâíèâàíèÿ
|
||||||
|
*int_var = *((volatile int32_t *)addr);
|
||||||
|
break;
|
||||||
case pt_uint32:
|
case pt_uint32:
|
||||||
if (addr_val & 0x1) // ïðîâåðÿåì âûðàâíèâàíèå ïî 2 ñëîâàì (4 áàéòà)
|
if ((addr_val & ALIGN_32BIT) != 0) // ïðîâåðÿåì âûðàâíèâàíèå
|
||||||
return 3; // îøèáêà âûðàâíèâàíèÿ
|
return DEBUG_ERR_ADDR_ALIGN; // îøèáêà âûðàâíèâàíèÿ
|
||||||
*int_var = *((volatile long *)addr);
|
*int_var = *((volatile uint32_t *)addr);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// case pt_int64: // 64 áèò (long long)
|
|
||||||
// case pt_uint64:
|
|
||||||
// if (addr_val & 0x3) // ïðîâåðêà âûðàâíèâàíèÿ ïî 4 ñëîâàì (8 áàéòàì)
|
|
||||||
// return 2; // îøèáêà âûðàâíèâàíèÿ
|
|
||||||
// // Òóò ïðîñòî ÷èòàåì, íî long long ìîæåò íå ïîìåñòèòüñÿ â *int_var
|
|
||||||
// // Ìîæíî çàìåíèòü ëîãèêó ïîä 64-áèòíîå ÷òåíèå ïðè íåîáõîäèìîñòè
|
|
||||||
// *int_var = *((volatile long long *)addr);
|
|
||||||
// break;
|
|
||||||
|
|
||||||
case pt_float: // float (4 áàéòà)
|
case pt_float: // float (4 áàéòà)
|
||||||
if (addr_val & 0x1) // ïðîâåðêà âûðàâíèâàíèÿ ïî 2 ñëîâàì
|
if ((addr_val & ALIGN_FLOAT) != 0) // ïðîâåðêà âûðàâíèâàíèÿ
|
||||||
return 4; // îøèáêà âûðàâíèâàíèÿ
|
return DEBUG_ERR_ADDR_ALIGN; // îøèáêà âûðàâíèâàíèÿ
|
||||||
*float_var = *((volatile float *)addr);
|
*float_var = *((volatile float *)addr);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return 1; // íåïîääåðæèâàåìûé òèï
|
return DEBUG_ERR_INVALID_VAR; // íåïîääåðæèâàåìûé òèï
|
||||||
// äëÿ óêàçàòåëåé è ìàññèâîâ íå ïîääåðæèâàåòñÿ ÷òåíèå
|
// äëÿ óêàçàòåëåé è ìàññèâîâ íå ïîääåðæèâàåòñÿ ÷òåíèå
|
||||||
// case pt_ptr_int8:
|
// case pt_ptr_int8:
|
||||||
// case pt_ptr_int16:
|
// case pt_ptr_int16:
|
||||||
@ -260,210 +430,22 @@ static int getDebugVar(DebugVar_t *var, long *int_var, float *float_var)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////// OUTDATE ////////////////
|
/**
|
||||||
|
* @brief Ïðîâåðÿåò, âõîäèò ëè àäðåñ â îäèí èç äîïóñòèìûõ äèàïàçîíîâ
|
||||||
|
*
|
||||||
|
* @param addr_val - Çíà÷åíèå àäðåñà äëÿ ïðîâåðêè
|
||||||
|
* @param ranges - Óêàçàòåëü íà ìàññèâ äèàïàçîíîâ AddrRange_t
|
||||||
|
* @param count - Êîëè÷åñòâî äèàïàçîíîâ â ìàññèâå
|
||||||
|
* @return 0 åñëè àäðåñ íàõîäèòñÿ â îäíîì èç äèàïàçîíîâ, èíà÷å 1
|
||||||
|
*/
|
||||||
|
static int is_addr_in_allowed_ranges(uint32_t addr_val, const AddrRange_t *ranges, int count)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
//
|
for (i = 0; i < count; i++) {
|
||||||
// // ïðèâåäåíèå ê îäíîìó IQ
|
if (addr_val >= ranges[i].start && addr_val <= ranges[i].end) {
|
||||||
// switch(var->iq_type)
|
return 0;
|
||||||
// {
|
}
|
||||||
// case t_iq_none:
|
}
|
||||||
// if(var->ptr_type == pt_float)
|
return 1;
|
||||||
// {
|
}
|
||||||
// iq_united = _IQ(float_numb);
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// iq_united = _IQ(iq_numb);
|
|
||||||
// }
|
|
||||||
// break;
|
|
||||||
// case t_iq1:
|
|
||||||
// iq_united = _IQ1toIQ(iq_numb);
|
|
||||||
// break;
|
|
||||||
// case t_iq2:
|
|
||||||
// iq_united = _IQ2toIQ(iq_numb);
|
|
||||||
// break;
|
|
||||||
// case t_iq3:
|
|
||||||
// iq_united = _IQ3toIQ(iq_numb);
|
|
||||||
// break;
|
|
||||||
// case t_iq4:
|
|
||||||
// iq_united = _IQ4toIQ(iq_numb);
|
|
||||||
// break;
|
|
||||||
// case t_iq5:
|
|
||||||
// iq_united = _IQ5toIQ(iq_numb);
|
|
||||||
// break;
|
|
||||||
// case t_iq6:
|
|
||||||
// iq_united = _IQ6toIQ(iq_numb);
|
|
||||||
// break;
|
|
||||||
// case t_iq7:
|
|
||||||
// iq_united = _IQ7toIQ(iq_numb);
|
|
||||||
// break;
|
|
||||||
// case t_iq8:
|
|
||||||
// iq_united = _IQ8toIQ(iq_numb);
|
|
||||||
// break;
|
|
||||||
// case t_iq9:
|
|
||||||
// iq_united = _IQ9toIQ(iq_numb);
|
|
||||||
// break;
|
|
||||||
// case t_iq10:
|
|
||||||
// iq_united = _IQ10toIQ(iq_numb);
|
|
||||||
// break;
|
|
||||||
// case t_iq11:
|
|
||||||
// iq_united = _IQ11toIQ(iq_numb);
|
|
||||||
// break;
|
|
||||||
// case t_iq12:
|
|
||||||
// iq_united = _IQ12toIQ(iq_numb);
|
|
||||||
// break;
|
|
||||||
// case t_iq13:
|
|
||||||
// iq_united = _IQ13toIQ(iq_numb);
|
|
||||||
// break;
|
|
||||||
// case t_iq14:
|
|
||||||
// iq_united = _IQ14toIQ(iq_numb);
|
|
||||||
// break;
|
|
||||||
// case t_iq15:
|
|
||||||
// iq_united = _IQ15toIQ(iq_numb);
|
|
||||||
// break;
|
|
||||||
// case t_iq16:
|
|
||||||
// iq_united = _IQ16toIQ(iq_numb);
|
|
||||||
// break;
|
|
||||||
// case t_iq17:
|
|
||||||
// iq_united = _IQ17toIQ(iq_numb);
|
|
||||||
// break;
|
|
||||||
// case t_iq18:
|
|
||||||
// iq_united = _IQ18toIQ(iq_numb);
|
|
||||||
// break;
|
|
||||||
// case t_iq19:
|
|
||||||
// iq_united = _IQ19toIQ(iq_numb);
|
|
||||||
// break;
|
|
||||||
// case t_iq20:
|
|
||||||
// iq_united = _IQ20toIQ(iq_numb);
|
|
||||||
// break;
|
|
||||||
// case t_iq21:
|
|
||||||
// iq_united = _IQ21toIQ(iq_numb);
|
|
||||||
// break;
|
|
||||||
// case t_iq22:
|
|
||||||
// iq_united = _IQ22toIQ(iq_numb);
|
|
||||||
// break;
|
|
||||||
// case t_iq23:
|
|
||||||
// iq_united = _IQ23toIQ(iq_numb);
|
|
||||||
// break;
|
|
||||||
// case t_iq24:
|
|
||||||
// iq_united = _IQ24toIQ(iq_numb);
|
|
||||||
// break;
|
|
||||||
// case t_iq25:
|
|
||||||
// iq_united = _IQ25toIQ(iq_numb);
|
|
||||||
// break;
|
|
||||||
// case t_iq26:
|
|
||||||
// iq_united = _IQ26toIQ(iq_numb);
|
|
||||||
// break;
|
|
||||||
// case t_iq27:
|
|
||||||
// iq_united = _IQ27toIQ(iq_numb);
|
|
||||||
// break;
|
|
||||||
// case t_iq28:
|
|
||||||
// iq_united = _IQ28toIQ(iq_numb);
|
|
||||||
// break;
|
|
||||||
// case t_iq29:
|
|
||||||
// iq_united = _IQ29toIQ(iq_numb);
|
|
||||||
// break;
|
|
||||||
// case t_iq30:
|
|
||||||
// iq_united = _IQ30toIQ(iq_numb);
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // ïðèâåäåíèå îáùåãî IQ ê çàïðàøèâàåìîìó
|
|
||||||
// switch(var->return_type)
|
|
||||||
// {
|
|
||||||
// case t_iq_none:
|
|
||||||
// iq_final = (long)_IQtoF(iq_united);
|
|
||||||
// break;
|
|
||||||
// case t_iq1:
|
|
||||||
// iq_final = _IQtoIQ1(iq_united);
|
|
||||||
// break;
|
|
||||||
// case t_iq2:
|
|
||||||
// iq_final = _IQtoIQ2(iq_united);
|
|
||||||
// break;
|
|
||||||
// case t_iq3:
|
|
||||||
// iq_final = _IQtoIQ3(iq_united);
|
|
||||||
// break;
|
|
||||||
// case t_iq4:
|
|
||||||
// iq_final = _IQtoIQ4(iq_united);
|
|
||||||
// break;
|
|
||||||
// case t_iq5:
|
|
||||||
// iq_final = _IQtoIQ5(iq_united);
|
|
||||||
// break;
|
|
||||||
// case t_iq6:
|
|
||||||
// iq_final = _IQtoIQ6(iq_united);
|
|
||||||
// break;
|
|
||||||
// case t_iq7:
|
|
||||||
// iq_final = _IQtoIQ7(iq_united);
|
|
||||||
// break;
|
|
||||||
// case t_iq8:
|
|
||||||
// iq_final = _IQtoIQ8(iq_united);
|
|
||||||
// break;
|
|
||||||
// case t_iq9:
|
|
||||||
// iq_final = _IQtoIQ9(iq_united);
|
|
||||||
// break;
|
|
||||||
// case t_iq10:
|
|
||||||
// iq_final = _IQtoIQ10(iq_united);
|
|
||||||
// break;
|
|
||||||
// case t_iq11:
|
|
||||||
// iq_final = _IQtoIQ11(iq_united);
|
|
||||||
// break;
|
|
||||||
// case t_iq12:
|
|
||||||
// iq_final = _IQtoIQ12(iq_united);
|
|
||||||
// break;
|
|
||||||
// case t_iq13:
|
|
||||||
// iq_final = _IQtoIQ13(iq_united);
|
|
||||||
// break;
|
|
||||||
// case t_iq14:
|
|
||||||
// iq_final = _IQtoIQ14(iq_united);
|
|
||||||
// break;
|
|
||||||
// case t_iq15:
|
|
||||||
// iq_final = _IQtoIQ15(iq_united);
|
|
||||||
// break;
|
|
||||||
// case t_iq16:
|
|
||||||
// iq_final = _IQtoIQ16(iq_united);
|
|
||||||
// break;
|
|
||||||
// case t_iq17:
|
|
||||||
// iq_final = _IQtoIQ17(iq_united);
|
|
||||||
// break;
|
|
||||||
// case t_iq18:
|
|
||||||
// iq_final = _IQtoIQ18(iq_united);
|
|
||||||
// break;
|
|
||||||
// case t_iq19:
|
|
||||||
// iq_final = _IQtoIQ19(iq_united);
|
|
||||||
// break;
|
|
||||||
// case t_iq20:
|
|
||||||
// iq_final = _IQtoIQ20(iq_united);
|
|
||||||
// break;
|
|
||||||
// case t_iq21:
|
|
||||||
// iq_final = _IQtoIQ21(iq_united);
|
|
||||||
// break;
|
|
||||||
// case t_iq22:
|
|
||||||
// iq_final = _IQtoIQ22(iq_united);
|
|
||||||
// break;
|
|
||||||
// case t_iq23:
|
|
||||||
// iq_final = _IQtoIQ23(iq_united);
|
|
||||||
// break;
|
|
||||||
// case t_iq24:
|
|
||||||
// iq_final = _IQtoIQ24(iq_united);
|
|
||||||
// break;
|
|
||||||
// case t_iq25:
|
|
||||||
// iq_final = _IQtoIQ25(iq_united);
|
|
||||||
// break;
|
|
||||||
// case t_iq26:
|
|
||||||
// iq_final = _IQtoIQ26(iq_united);
|
|
||||||
// break;
|
|
||||||
// case t_iq27:
|
|
||||||
// iq_final = _IQtoIQ27(iq_united);
|
|
||||||
// break;
|
|
||||||
// case t_iq28:
|
|
||||||
// iq_final = _IQtoIQ28(iq_united);
|
|
||||||
// break;
|
|
||||||
// case t_iq29:
|
|
||||||
// iq_final = _IQtoIQ29(iq_united);
|
|
||||||
// break;
|
|
||||||
// case t_iq30:
|
|
||||||
// iq_final = _IQtoIQ30(iq_united);
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// *ret_var = iq_final;
|
|
||||||
|
148
debug_tools.h
148
debug_tools.h
@ -1,8 +1,65 @@
|
|||||||
#ifndef DEBUG_TOOLS
|
#ifndef DEBUG_TOOLS
|
||||||
#define DEBUG_TOOLS
|
#define DEBUG_TOOLS
|
||||||
#include "IQmathLib.h"
|
#include <stdint.h>
|
||||||
#include "DSP281x_Device.h"
|
#include <limits.h>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#define ALLOWED_ADDRESS_RANGES { \
|
||||||
|
{0x000000, 0x0007FF}, \
|
||||||
|
{0x008120, 0x009FFC}, \
|
||||||
|
{0x3F8000, 0x3F9FFF}, \
|
||||||
|
{0x3FF000, 0x3FFFFF}, \
|
||||||
|
{0x080002, 0x09FFFF}, \
|
||||||
|
{0x0F0000, 0x0FFEFF}, \
|
||||||
|
{0x100002, 0x103FFF}, \
|
||||||
|
{0x102000, 0x103FFF} \
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if UINT8_MAX // Åñëè åñòü òèï 8 áèò - çí÷à÷èò àäðåñàöèÿ ïî 8 áèò
|
||||||
|
|
||||||
|
#define ALIGN_8BIT 0x0 ///< Âûðàâíèâàíèå áåç îãðàíè÷åíèé (ëþáîé àäðåñ)
|
||||||
|
#define ALIGN_16BIT 0x1 ///< Âûðàâíèâàíèå: àäðåñ äîëæåí áûòü êðàòåí 2 (addr & 0x1 == 0)
|
||||||
|
#define ALIGN_32BIT 0x3 ///< Âûðàâíèâàíèå: àäðåñ äîëæåí áûòü êðàòåí 4 (addr & 0x3 == 0)
|
||||||
|
#define ALIGN_64BIT 0x7 ///< Âûðàâíèâàíèå: àäðåñ äîëæåí áûòü êðàòåí 8 (addr & 0x7 == 0)
|
||||||
|
#define ALIGN_FLOAT ALIGN_32BIT
|
||||||
|
|
||||||
|
#else // Åñëè íåò òèïà 8 áèò - çíà÷èò àäðåñàöèÿ ïî 16 áèò
|
||||||
|
|
||||||
|
#define ALIGN_8BIT 0x0 ///< Âûðàâíèâàíèå áåç îãðàíè÷åíèé (ëþáîé àäðåñ)
|
||||||
|
#define ALIGN_16BIT 0x0 ///< Âûðàâíèâàíèå áåç îãðàíè÷åíèé (ëþáîé àäðåñ)
|
||||||
|
#define ALIGN_32BIT 0x1 ///< Âûðàâíèâàíèå: àäðåñ äîëæåí áûòü êðàòåí 4 (addr & 0x1 == 0)
|
||||||
|
#define ALIGN_64BIT 0x3 ///< Âûðàâíèâàíèå: àäðåñ äîëæåí áûòü êðàòåí 8 (addr & 0x3 == 0)
|
||||||
|
#define ALIGN_FLOAT ALIGN_32BIT
|
||||||
|
|
||||||
|
#endif //STM32/TMS32
|
||||||
|
|
||||||
|
#if !UINT8_MAX
|
||||||
|
typedef unsigned char uint8_t;
|
||||||
|
typedef signed char int8_t;
|
||||||
|
#endif
|
||||||
|
#if !defined(NULL)
|
||||||
|
#define NULL 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define DEBUG_SIGNED_VAR (1<<7)
|
||||||
|
|
||||||
|
#define DEBUG_OK (0)
|
||||||
|
#define DEBUG_ERR (1<<7)
|
||||||
|
#define DEBUG_ERR_VAR_NUMB (1<<0) | DEBUG_ERR
|
||||||
|
#define DEBUG_ERR_INVALID_VAR (1<<1) | DEBUG_ERR
|
||||||
|
#define DEBUG_ERR_ADDR (1<<2) | DEBUG_ERR
|
||||||
|
#define DEBUG_ERR_ADDR_ALIGN (1<<3) | DEBUG_ERR
|
||||||
|
#define DEBUG_ERR_INTERNAL (1<<4) | DEBUG_ERR
|
||||||
|
#define DEBUG_ERR_DATATIME (1<<5) | DEBUG_ERR
|
||||||
|
#define DEBUG_ERR_RS (1<<6) | DEBUG_ERR
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Òèï äàííûõ, íà êîòîðûé óêàçûâàåò óêàçàòåëü ïåðåìåííîé îòëàäêè.
|
||||||
|
*/
|
||||||
typedef enum
|
typedef enum
|
||||||
{
|
{
|
||||||
pt_unknown, // unknown
|
pt_unknown, // unknown
|
||||||
@ -14,7 +71,7 @@ typedef enum
|
|||||||
pt_uint16, // unsigned int
|
pt_uint16, // unsigned int
|
||||||
pt_uint32, // unsigned long
|
pt_uint32, // unsigned long
|
||||||
pt_uint64, // unsigned long
|
pt_uint64, // unsigned long
|
||||||
pt_float, // float
|
pt_float, // floatf
|
||||||
pt_struct, // struct
|
pt_struct, // struct
|
||||||
pt_union, // struct
|
pt_union, // struct
|
||||||
// pt_ptr_int8, // signed char*
|
// pt_ptr_int8, // signed char*
|
||||||
@ -31,6 +88,9 @@ typedef enum
|
|||||||
// pt_arr_uint32, // unsigned long[]
|
// pt_arr_uint32, // unsigned long[]
|
||||||
}DebugVarPtrType_t;
|
}DebugVarPtrType_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Òèïû IQ-ïðåäñòàâëåíèÿ ïåðåìåííîé îòëàäêè.
|
||||||
|
*/
|
||||||
typedef enum
|
typedef enum
|
||||||
{
|
{
|
||||||
t_iq_none,
|
t_iq_none,
|
||||||
@ -67,45 +127,83 @@ typedef enum
|
|||||||
t_iq30
|
t_iq30
|
||||||
}DebugVarIQType_t;
|
}DebugVarIQType_t;
|
||||||
|
|
||||||
typedef char DebugVarName_t[11];
|
typedef char DebugVarName_t[11]; ///< Èìÿ ïåðåìåííîé îòëàäêè (äî 10 ñèìâîëîâ + \0)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Îïèñàíèå ïåðåìåííîé îòëàäêè.
|
||||||
|
*/
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
char* Ptr;
|
uint8_t* Ptr; ///< Óêàçàòåëü íà çíà÷åíèå ïåðåìåííîé
|
||||||
DebugVarPtrType_t ptr_type;
|
DebugVarPtrType_t ptr_type; ///< Òèï çíà÷åíèÿ
|
||||||
DebugVarIQType_t iq_type;
|
DebugVarIQType_t iq_type; ///< Òèï IQ ïåðåìåííîé (åñëè åñòü)
|
||||||
DebugVarIQType_t return_type;
|
DebugVarIQType_t return_type;///< Òèï IQ âîçâðàùàåìîãî çíà÷åíèÿ
|
||||||
DebugVarName_t name;
|
DebugVarName_t name; ///< Èìÿ ïåðåìåííîé
|
||||||
}DebugVar_t;
|
} DebugVar_t;
|
||||||
|
|
||||||
|
typedef long DebugValue_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Ñòðóêòóðà äàòû è âðåìåíè.
|
||||||
|
*/
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int year;
|
uint16_t year; ///< Ãîä (íàïðèìåð, 2025)
|
||||||
char month;
|
uint8_t month; ///< Ìåñÿö (1-12)
|
||||||
char day;
|
uint8_t day; ///< Äåíü (1-31)
|
||||||
char hour;
|
uint8_t hour; ///< ×àñû (0-23)
|
||||||
char minute;
|
uint8_t minute; ///< Ìèíóòû (0-59)
|
||||||
} DateTime_t;
|
} DateTime_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Ñòðóêòóðà, îïèñûâàþùàÿ äèàïàçîí àäðåñîâ ïàìÿòè.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
uint32_t start; ///< Íà÷àëüíûé àäðåñ äèàïàçîíà
|
||||||
|
uint32_t end; ///< Êîíå÷íûé àäðåñ äèàïàçîíà (âêëþ÷èòåëüíî)
|
||||||
|
} AddrRange_t;
|
||||||
|
/**
|
||||||
|
* @brief Ñòðóêòóðà íèæíåãî óðîâíÿ îòëàäêè.
|
||||||
|
*/
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
DateTime_t build_date;
|
DateTime_t build_date; ///< Äàòà ñáîðêè
|
||||||
unsigned int isVerified;
|
unsigned int isVerified; ///< Ôëàã èíèöèàëèçàöèè íèçêîóðîâíåíîé îòëàäêè (0 — íåò, 1 — óñïåøíî)
|
||||||
DebugVar_t dbg_var;
|
DebugVar_t dbg_var; ///< Ïåðåìåííàÿ äëÿ îòëàäêè
|
||||||
}DebugLowLevel_t;
|
}DebugLowLevel_t;
|
||||||
extern DebugLowLevel_t debug_ll;
|
extern DebugLowLevel_t debug_ll; ///< Ãëîáàëüíûé ýêçåìïëÿð îòëàäêè íèæíåãî óðîâíÿ
|
||||||
|
|
||||||
|
|
||||||
|
/** @brief Ìàêðîñ èíèöèàëèçàöèè äàòû */
|
||||||
#define DATE_INIT {BUILD_YEAR, BUILD_MONTH, BUILD_DATA, BUILD_HOURS, BUILD_MINUTES}
|
#define DATE_INIT {BUILD_YEAR, BUILD_MONTH, BUILD_DATA, BUILD_HOURS, BUILD_MINUTES}
|
||||||
|
/** @brief Ìàêðîñ èíèöèàëèçàöèè ïåðåìåííîé îòëàäêè */
|
||||||
#define DEBUG_VAR_INIT {0, pt_uint16, t_iq_none, t_iq_none, "\0"}
|
#define DEBUG_VAR_INIT {0, pt_uint16, t_iq_none, t_iq_none, "\0"}
|
||||||
|
/** @brief Ìàêðîñ èíèöèàëèçàöèè íèæíåãî óðîâíÿ îòëàäêè */
|
||||||
#define DEBUG_LOWLEVEL_INIT {DATE_INIT, 0, DEBUG_VAR_INIT}
|
#define DEBUG_LOWLEVEL_INIT {DATE_INIT, 0, DEBUG_VAR_INIT}
|
||||||
|
|
||||||
|
|
||||||
extern int DebugVar_Qnt;
|
extern int DebugVar_Qnt; ///< Êîëè÷åñòâî ïåðåìåííûõ îòëàäêè
|
||||||
extern DebugVar_t dbg_vars[];
|
extern DebugVar_t dbg_vars[]; ///< Ìàññèâ ïåðåìåííûõ îòëàäêè
|
||||||
|
|
||||||
|
|
||||||
|
/* Ïðèìåð èñïîëüçîâàíèÿ îòëàäêè */
|
||||||
void Debug_Test_Example(void);
|
void Debug_Test_Example(void);
|
||||||
|
|
||||||
int Debug_ReadVar(int var_ind, long *return_long);
|
/* ×èòàåò çíà÷åíèå ïåðåìåííîé ïî èíäåêñó */
|
||||||
int Debug_ReadVarName(int var_ind, DebugVarName_t name_ptr);
|
int Debug_ReadVar(int var_ind, int32_t *return_long);
|
||||||
int Debug_LowLevel_ReadVar(long *return_long);
|
/* ×èòàåò èìÿ ïåðåìåííîé ïî èíäåêñó */
|
||||||
int Debug_LowLevel_Initialize(const char* external_date);
|
int Debug_ReadVarName(int var_ind, DebugVarName_t name_ptr, int *length);
|
||||||
|
/* ×èòàåò âîçâðàùàåìûé òèï (IQ) ïåðåìåííîé ïî èíäåêñó */
|
||||||
|
int Debug_ReadVarReturnType(int var_ind, int *vartype);
|
||||||
|
/* ×èòàåò òèï ïåðåìåííîé ïî èíäåêñó */
|
||||||
|
int Debug_ReadVarType(int var_ind, int *vartype);
|
||||||
|
|
||||||
|
|
||||||
|
/* ×èòàåò çíà÷åíèå ïåðåìåííîé ñ íèæíåãî óðîâíÿ */
|
||||||
|
int Debug_LowLevel_ReadVar(int32_t *return_long);
|
||||||
|
/* Èíèöèàëèçèðóåò îòëàäêó íèæíåãî óðîâíÿ */
|
||||||
|
int Debug_LowLevel_Initialize(DateTime_t *external_date);
|
||||||
|
/* ×èòàåò âîçâðàùàåìûé òèï (IQ) íèçêîóðîâíåíî çàäàííîé ïåðåìåííîé */
|
||||||
|
int Debug_LowLevel_ReadVarReturnType(int *vartype);
|
||||||
|
/* ×èòàåò òèï íèçêîóðîâíåíî çàäàííîé ïåðåìåííîé.*/
|
||||||
|
int Debug_LowLevel_ReadVarType(int *vartype);
|
||||||
#endif //DEBUG_TOOLS
|
#endif //DEBUG_TOOLS
|
||||||
|
380
parse_xml/Src/parse_xml.py
Normal file
380
parse_xml/Src/parse_xml.py
Normal file
@ -0,0 +1,380 @@
|
|||||||
|
# pyinstaller --onefile --distpath ./parse_xml --workpath ./build --specpath ./build parse_xml/Src/parse_xml.py
|
||||||
|
# python -m nuitka --standalone --onefile --output-dir=./build parse_xml/Src/parse_xml.py
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
import xml.dom.minidom
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
if len(sys.argv) < 3:
|
||||||
|
print("Usage: python simplify_dwarf.py <input.xml> <info.txt> [output.xml]")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
input_path = sys.argv[1]
|
||||||
|
info_path = sys.argv[2]
|
||||||
|
|
||||||
|
base_type_sizes = {
|
||||||
|
"char": 2,
|
||||||
|
"short": 2,
|
||||||
|
"int": 2,
|
||||||
|
"long": 4,
|
||||||
|
"long long": 8,
|
||||||
|
"float": 4,
|
||||||
|
"double": 8,
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(sys.argv) >= 4:
|
||||||
|
output_path = sys.argv[3]
|
||||||
|
else:
|
||||||
|
input_dir = os.path.dirname(os.path.abspath(input_path))
|
||||||
|
output_path = os.path.join(input_dir, "simplified.xml")
|
||||||
|
|
||||||
|
tree = ET.parse(input_path)
|
||||||
|
root = tree.getroot()
|
||||||
|
|
||||||
|
def extract_timestamp(info_path):
|
||||||
|
with open(info_path, "r", encoding="utf-8") as f:
|
||||||
|
for line in f:
|
||||||
|
if "Time Stamp:" in line:
|
||||||
|
parts = line.split("Time Stamp:")
|
||||||
|
if len(parts) > 1:
|
||||||
|
timestamp = parts[1].strip()
|
||||||
|
return timestamp
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
die_by_id = {die.attrib.get("id"): die for die in root.iter("die") if "id" in die.attrib}
|
||||||
|
|
||||||
|
def get_attr(die, attr_type):
|
||||||
|
for attr in die.findall("attribute"):
|
||||||
|
type_elem = attr.find("type")
|
||||||
|
if type_elem is not None and type_elem.text == attr_type:
|
||||||
|
return attr.find("value")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_die_size(die):
|
||||||
|
"""Вернуть размер DIE в байтах из атрибута DW_AT_byte_size или по ключевым словам имени типа."""
|
||||||
|
|
||||||
|
# Сначала пытаемся получить размер из DW_AT_byte_size
|
||||||
|
for attr in die.findall("attribute"):
|
||||||
|
type_elem = attr.find("type")
|
||||||
|
if type_elem is not None and type_elem.text == "DW_AT_byte_size":
|
||||||
|
const_elem = attr.find("value/const")
|
||||||
|
if const_elem is not None:
|
||||||
|
return int(const_elem.text, 0)
|
||||||
|
|
||||||
|
# Если не нашли, пробуем определить размер по ключевым словам в имени типа
|
||||||
|
name_elem = die.find("attribute[@name='DW_AT_name']/value/const")
|
||||||
|
if name_elem is not None:
|
||||||
|
type_name = name_elem.text.lower()
|
||||||
|
|
||||||
|
for key, size in base_type_sizes.items():
|
||||||
|
if key in type_name:
|
||||||
|
return size
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def resolve_type_die(type_id):
|
||||||
|
"""Получить DIE типа, разрешая typedef, const и volatile."""
|
||||||
|
visited = set()
|
||||||
|
while type_id and type_id not in visited:
|
||||||
|
visited.add(type_id)
|
||||||
|
die = die_by_id.get(type_id)
|
||||||
|
if die is None:
|
||||||
|
return None
|
||||||
|
tag = die.findtext("tag")
|
||||||
|
if tag in ("DW_TAG_volatile_type", "DW_TAG_const_type", "DW_TAG_typedef", "DW_TAG_TI_far_type"):
|
||||||
|
ref = get_attr(die, "DW_AT_type")
|
||||||
|
if ref is not None and ref.find("ref") is not None:
|
||||||
|
type_id = ref.find("ref").attrib.get("idref")
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return die
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Словарь для простых базовых типов по тегам (пример)
|
||||||
|
base_types_map = {
|
||||||
|
"DW_TAG_base_type": lambda die: die.find("attribute[@type='DW_AT_name']/value/string").text if die.find("attribute[@type='DW_AT_name']/value/string") is not None else "unknown",
|
||||||
|
"DW_TAG_structure_type": lambda die: "struct",
|
||||||
|
"DW_TAG_union_type": lambda die: "union",
|
||||||
|
"DW_TAG_pointer_type": lambda die: "pointer",
|
||||||
|
"DW_TAG_array_type": lambda die: "array",
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_type_name(type_id):
|
||||||
|
die = resolve_type_die(type_id)
|
||||||
|
if die is None:
|
||||||
|
return "unknown"
|
||||||
|
|
||||||
|
tag = die.findtext("tag")
|
||||||
|
|
||||||
|
if tag == "DW_TAG_pointer_type":
|
||||||
|
ref = get_attr(die, "DW_AT_type")
|
||||||
|
if ref is not None and ref.find("ref") is not None:
|
||||||
|
pointee_id = ref.find("ref").attrib.get("idref")
|
||||||
|
name = get_type_name(pointee_id)
|
||||||
|
return name + "*" if name != "unknown" else name
|
||||||
|
else:
|
||||||
|
return "void*"
|
||||||
|
|
||||||
|
elif tag == "DW_TAG_base_type":
|
||||||
|
name_attr = get_attr(die, "DW_AT_name")
|
||||||
|
if name_attr is not None:
|
||||||
|
return name_attr.findtext("string")
|
||||||
|
else:
|
||||||
|
return "base_type_unknown"
|
||||||
|
|
||||||
|
elif tag == "DW_TAG_structure_type":
|
||||||
|
name_attr = get_attr(die, "DW_AT_name")
|
||||||
|
name = name_attr.findtext("string") if name_attr is not None else "anonymous_struct"
|
||||||
|
return f"struct {name}"
|
||||||
|
|
||||||
|
elif tag == "DW_TAG_union_type":
|
||||||
|
name_attr = get_attr(die, "DW_AT_name")
|
||||||
|
name = name_attr.findtext("string") if name_attr is not None else "anonymous_union"
|
||||||
|
return f"union {name}"
|
||||||
|
|
||||||
|
elif tag == "DW_TAG_array_type":
|
||||||
|
ref = get_attr(die, "DW_AT_type")
|
||||||
|
if ref is not None and ref.find("ref") is not None:
|
||||||
|
element_type_id = ref.find("ref").attrib.get("idref")
|
||||||
|
element_type_name = get_type_name(element_type_id)
|
||||||
|
return f"{element_type_name}[]"
|
||||||
|
else:
|
||||||
|
return "array[]"
|
||||||
|
|
||||||
|
# Добавляем поддержку enum
|
||||||
|
elif tag == "DW_TAG_enumeration_type":
|
||||||
|
name_attr = get_attr(die, "DW_AT_name")
|
||||||
|
name = name_attr.findtext("string") if name_attr is not None else "anonymous_enum"
|
||||||
|
return f"enum {name}"
|
||||||
|
|
||||||
|
else:
|
||||||
|
return "unknown"
|
||||||
|
|
||||||
|
def parse_offset(offset_text):
|
||||||
|
if offset_text and offset_text.startswith("DW_OP_plus_uconst "):
|
||||||
|
return int(offset_text.split()[-1], 0)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def get_array_dimensions(array_die):
|
||||||
|
"""
|
||||||
|
Собрать размеры всех измерений массива.
|
||||||
|
Возвращает список размеров [dim0, dim1, ...] во внешне-внутреннем порядке.
|
||||||
|
"""
|
||||||
|
dims = []
|
||||||
|
|
||||||
|
# Ищем DW_TAG_subrange_type — для каждого измерения
|
||||||
|
for child in array_die.findall("die"):
|
||||||
|
if child.findtext("tag") != "DW_TAG_subrange_type":
|
||||||
|
continue
|
||||||
|
|
||||||
|
dim_size = None
|
||||||
|
|
||||||
|
# Попытка получить upper_bound
|
||||||
|
ub_attr = get_attr(child, "DW_AT_upper_bound")
|
||||||
|
if ub_attr is not None:
|
||||||
|
val = ub_attr.find("value/const")
|
||||||
|
if val is not None:
|
||||||
|
try:
|
||||||
|
# В DWARF верхняя граница включительно, значит размер = upper_bound + 1
|
||||||
|
dim_size = int(val.text, 0) + 1
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Если не получилось — попытаться из count
|
||||||
|
if dim_size is None:
|
||||||
|
ct_attr = get_attr(child, "DW_AT_count")
|
||||||
|
if ct_attr is not None:
|
||||||
|
val = ct_attr.find("value/const")
|
||||||
|
if val is not None:
|
||||||
|
try:
|
||||||
|
dim_size = int(val.text, 0)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Если ничего не найдено — ставим 0
|
||||||
|
if dim_size is None:
|
||||||
|
dim_size = 0
|
||||||
|
|
||||||
|
dims.append(dim_size)
|
||||||
|
|
||||||
|
# Если subrange не нашли (например, в случае typedef), попробуем через размер типа
|
||||||
|
if not dims:
|
||||||
|
arr_size = get_die_size(array_die)
|
||||||
|
elem_size = None
|
||||||
|
element_type_ref = get_attr(array_die, "DW_AT_type")
|
||||||
|
if element_type_ref is not None and element_type_ref.find("ref") is not None:
|
||||||
|
element_type_id = element_type_ref.find("ref").attrib.get("idref")
|
||||||
|
elem_die = resolve_type_die(element_type_id)
|
||||||
|
if elem_die is not None:
|
||||||
|
elem_size = get_die_size(elem_die)
|
||||||
|
|
||||||
|
if arr_size is not None and elem_size:
|
||||||
|
dims.append(arr_size // elem_size)
|
||||||
|
else:
|
||||||
|
dims.append(0)
|
||||||
|
|
||||||
|
# Рекурсия — если элементный тип массива тоже массив, добавляем размеры вложенного
|
||||||
|
element_type_ref = get_attr(array_die, "DW_AT_type")
|
||||||
|
if element_type_ref is not None and element_type_ref.find("ref") is not None:
|
||||||
|
element_type_id = element_type_ref.find("ref").attrib.get("idref")
|
||||||
|
element_type_die = resolve_type_die(element_type_id)
|
||||||
|
if element_type_die is not None and element_type_die.findtext("tag") == "DW_TAG_array_type":
|
||||||
|
dims.extend(get_array_dimensions(element_type_die))
|
||||||
|
|
||||||
|
return dims
|
||||||
|
|
||||||
|
|
||||||
|
def get_base_type_die(array_die):
|
||||||
|
"""Спускаемся по цепочке DW_AT_type, пока не дойдем до не-массива (базового типа)."""
|
||||||
|
current_die = array_die
|
||||||
|
while True:
|
||||||
|
ref = get_attr(current_die, "DW_AT_type")
|
||||||
|
if ref is None or ref.find("ref") is None:
|
||||||
|
break
|
||||||
|
next_die = resolve_type_die(ref.find("ref").attrib.get("idref"))
|
||||||
|
if next_die is None:
|
||||||
|
break
|
||||||
|
if next_die.findtext("tag") == "DW_TAG_array_type":
|
||||||
|
current_die = next_die
|
||||||
|
else:
|
||||||
|
return next_die
|
||||||
|
return current_die
|
||||||
|
|
||||||
|
|
||||||
|
def handle_array_type(member_elem, resolved_type, offset=0):
|
||||||
|
dims = get_array_dimensions(resolved_type)
|
||||||
|
|
||||||
|
# Определяем базовый тип (не массив)
|
||||||
|
base_die = get_base_type_die(resolved_type)
|
||||||
|
base_name = "unknown"
|
||||||
|
base_size = None
|
||||||
|
if base_die is not None:
|
||||||
|
base_id = base_die.attrib.get("id")
|
||||||
|
if base_id:
|
||||||
|
base_name = get_type_name(base_id)
|
||||||
|
base_size = get_die_size(base_die)
|
||||||
|
else:
|
||||||
|
base_name = get_type_name(base_die.attrib.get("id", ""))
|
||||||
|
|
||||||
|
member_elem.set("type", base_name + "[]" * len(dims))
|
||||||
|
|
||||||
|
# Вычисляем общий размер массива — произведение размеров * размер базового элемента
|
||||||
|
if base_size is None:
|
||||||
|
base_size = 0
|
||||||
|
|
||||||
|
total_elements = 1
|
||||||
|
for d in dims:
|
||||||
|
if d == 0:
|
||||||
|
total_elements = 0
|
||||||
|
break
|
||||||
|
total_elements *= d
|
||||||
|
|
||||||
|
total_size = total_elements * base_size if base_size is not None else 0
|
||||||
|
if total_size:
|
||||||
|
member_elem.set("size", str(total_size))
|
||||||
|
else:
|
||||||
|
# fallback: если не удалось, можно попробовать get_die_size
|
||||||
|
arr_size = get_die_size(resolved_type)
|
||||||
|
if arr_size:
|
||||||
|
member_elem.set("size", str(arr_size))
|
||||||
|
|
||||||
|
# Записываем размеры измерений size1, size2 ...
|
||||||
|
for i, dim in enumerate(dims, 1):
|
||||||
|
member_elem.set(f"size{i}", str(dim))
|
||||||
|
|
||||||
|
member_elem.set("kind", "array")
|
||||||
|
|
||||||
|
# Если элемент базового типа — структура, разворачиваем её поля
|
||||||
|
if base_die is not None and base_die.findtext("tag") == "DW_TAG_structure_type":
|
||||||
|
add_members_recursive(member_elem, base_die, offset)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def add_members_recursive(parent_elem, struct_die, base_offset=0):
|
||||||
|
is_union = struct_die.findtext("tag") == "DW_TAG_union_type"
|
||||||
|
size = get_die_size(struct_die)
|
||||||
|
if size is not None:
|
||||||
|
parent_elem.set("size", hex(size))
|
||||||
|
|
||||||
|
for member in struct_die.findall("die"):
|
||||||
|
if member.findtext("tag") != "DW_TAG_member":
|
||||||
|
continue
|
||||||
|
|
||||||
|
name_attr = get_attr(member, "DW_AT_name")
|
||||||
|
offset_attr = get_attr(member, "DW_AT_data_member_location")
|
||||||
|
type_attr = get_attr(member, "DW_AT_type")
|
||||||
|
if name_attr is None or offset_attr is None or type_attr is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
name = name_attr.findtext("string")
|
||||||
|
offset = parse_offset(offset_attr.findtext("block")) + base_offset
|
||||||
|
type_id = type_attr.find("ref").attrib.get("idref")
|
||||||
|
resolved_type = resolve_type_die(type_id)
|
||||||
|
type_name = get_type_name(type_id)
|
||||||
|
if type_name == "unknown":
|
||||||
|
continue
|
||||||
|
|
||||||
|
member_elem = ET.SubElement(parent_elem, "member", name=name, offset=hex(offset), type=type_name)
|
||||||
|
if is_union:
|
||||||
|
member_elem.set("kind", "union")
|
||||||
|
|
||||||
|
if resolved_type is not None:
|
||||||
|
tag = resolved_type.findtext("tag")
|
||||||
|
if tag == "DW_TAG_array_type":
|
||||||
|
handle_array_type(member_elem, resolved_type, offset)
|
||||||
|
elif tag in ("DW_TAG_structure_type", "DW_TAG_union_type"):
|
||||||
|
member_elem.set("type", type_name)
|
||||||
|
add_members_recursive(member_elem, resolved_type, offset)
|
||||||
|
|
||||||
|
output_root = ET.Element("variables")
|
||||||
|
for die in root.iter("die"):
|
||||||
|
if die.findtext("tag") != "DW_TAG_variable":
|
||||||
|
continue
|
||||||
|
|
||||||
|
name_attr = get_attr(die, "DW_AT_name")
|
||||||
|
addr_attr = get_attr(die, "DW_AT_location")
|
||||||
|
type_attr = get_attr(die, "DW_AT_type")
|
||||||
|
if name_attr is None or addr_attr is None or type_attr is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
name = name_attr.findtext("string")
|
||||||
|
if "$" in name:
|
||||||
|
continue
|
||||||
|
|
||||||
|
addr_text = addr_attr.findtext("block")
|
||||||
|
if not addr_text or not addr_text.startswith("DW_OP_addr "):
|
||||||
|
continue
|
||||||
|
|
||||||
|
addr = int(addr_text.split()[-1], 0)
|
||||||
|
type_id = type_attr.find("ref").attrib.get("idref")
|
||||||
|
resolved_type = resolve_type_die(type_id)
|
||||||
|
type_name = get_type_name(type_id)
|
||||||
|
if 0x800 <= addr < 0x8000 or type_name == "unknown":
|
||||||
|
continue
|
||||||
|
|
||||||
|
var_elem = ET.SubElement(output_root, "variable", name=name, address=hex(addr), type=type_name)
|
||||||
|
if resolved_type is not None:
|
||||||
|
tag = resolved_type.findtext("tag")
|
||||||
|
if tag == "DW_TAG_array_type":
|
||||||
|
handle_array_type(var_elem, resolved_type)
|
||||||
|
elif tag in ("DW_TAG_structure_type", "DW_TAG_union_type"):
|
||||||
|
add_members_recursive(var_elem, resolved_type)
|
||||||
|
|
||||||
|
timestamp = extract_timestamp(info_path)
|
||||||
|
timestamp_elem = ET.Element("timestamp")
|
||||||
|
timestamp_elem.text = timestamp
|
||||||
|
output_root.insert(0, timestamp_elem)
|
||||||
|
|
||||||
|
rough_string = ET.tostring(output_root, encoding="utf-8")
|
||||||
|
pretty_xml = xml.dom.minidom.parseString(rough_string).toprettyxml(indent=" ")
|
||||||
|
with open(output_path, "w", encoding="utf-8") as f:
|
||||||
|
f.write(pretty_xml)
|
||||||
|
|
||||||
|
os.remove(input_path)
|
||||||
|
os.remove(info_path)
|
||||||
|
print(f"Simplified and formatted XML saved to: {output_path}")
|
BIN
parse_xml/parse_xml.exe
Normal file
BIN
parse_xml/parse_xml.exe
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user