опять кууууча всего:
базово доделаны терминалки до более менее итогового состояния
This commit is contained in:
		
							parent
							
								
									e99de603e6
								
							
						
					
					
						commit
						502046091c
					
				
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -6,3 +6,4 @@ | ||||
| /DebugVarEdit_GUI.dist | ||||
| /DebugVarEdit_GUI.onefile-build | ||||
| /parse_xml/build/ | ||||
| /parse_xml/Src/__pycache__/ | ||||
|  | ||||
							
								
								
									
										
											BIN
										
									
								
								DebugVarEdit.exe
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								DebugVarEdit.exe
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							| @ -191,14 +191,39 @@ class VarEditor(QWidget): | ||||
| 
 | ||||
|         self.setLayout(layout) | ||||
| 
 | ||||
| 
 | ||||
|     def open_terminal(self, target): | ||||
|         target = target.lower() | ||||
|          | ||||
|         if target == "tms": | ||||
|             self.terminal_widget = _DemoWindow()  # _DemoWindow наследует QWidget | ||||
|             self.terminal_widget.show() | ||||
|             exe_name = "DebugVarTerminal.exe" | ||||
|             # Путь к exe в текущей директории запуска программы | ||||
|             exe_path = os.path.join(os.getcwd(), exe_name) | ||||
|              | ||||
|             if not os.path.isfile(exe_path): | ||||
|                 # Файл не найден — попросим пользователя выбрать путь к exe | ||||
|                 msg = QMessageBox() | ||||
|                 msg.setIcon(QMessageBox.Warning) | ||||
|                 msg.setWindowTitle("Файл не найден") | ||||
|                 msg.setText(f"Файл {exe_name} не найден в текущей папке.\nВыберите путь к {exe_name}.") | ||||
|                 msg.exec_() | ||||
|                  | ||||
|                 # Открываем диалог выбора файла | ||||
|                 selected_path, _ = QFileDialog.getOpenFileName( | ||||
|                     None, "Выберите файл " + exe_name, os.getcwd(), "Executable Files (*.exe)" | ||||
|                 ) | ||||
|                  | ||||
|                 if not selected_path: | ||||
|                     # Пользователь отменил выбор — ничего не делаем | ||||
|                     return | ||||
|                  | ||||
|                 exe_path = selected_path | ||||
|              | ||||
|             # Запускаем exe (отдельное окно терминала) | ||||
|             subprocess.Popen([exe_path], creationflags=subprocess.CREATE_NEW_CONSOLE) | ||||
|              | ||||
|         elif target == "modbus": | ||||
|             a=1 | ||||
|             a = 1 | ||||
| 
 | ||||
|          | ||||
|     def on_target_selected(self, target): | ||||
|  | ||||
| @ -1,34 +1,57 @@ | ||||
| """ | ||||
| VariablesXML + get_all_vars_data | ||||
| --------------------------------- | ||||
| Поддержка вложенных структур, указателей на структуры ("->"), | ||||
| и многомерных массивов (индексация [i][j]...). | ||||
| 
 | ||||
| Требования пользователя: | ||||
| - size  (без индекса) = общий размер массива в байтах (НЕ измерение!). | ||||
| - size1..sizeN        = размеры измерений массива. | ||||
| - В результирующем плоском списке (flattened) должны присутствовать ВСЕ промежуточные | ||||
|   пути: var, var[0], var[0][0], var[0][0].field, var[0][0].field->subfield, ... | ||||
| - Аналогично для членов структур. | ||||
| 
 | ||||
| Пример желаемого формата: | ||||
|     project | ||||
|     project.adc | ||||
|     project.adc[0] | ||||
|     project.adc[0][0] | ||||
|     project.adc[0][0].bus | ||||
|     project.adc[0][0].bus->status | ||||
| 
 | ||||
| Данный модуль реализует: | ||||
| - Разбор XML (parse) с извлечением размеров размерностей в поле `dims`. | ||||
| - Генерацию плоского списка словарей `flattened()`. | ||||
| - Построение иерархии словарей `get_all_vars_data()`. | ||||
| 
 | ||||
| """ | ||||
| 
 | ||||
| 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 | ||||
| from typing import List, Dict, Optional, Tuple, Any | ||||
| 
 | ||||
| DATE_FIELD_SET = {'year','month','day','hour','minute'} | ||||
| import var_setup  # ожидается split_path(...) | ||||
| from generate_debug_vars import choose_type_map, type_map  # используется для выбора карт типов | ||||
| 
 | ||||
| # --------------------------- константы ---------------------------- | ||||
| 
 | ||||
| DATE_FIELD_SET = {"year", "month", "day", "hour", "minute"} | ||||
| 
 | ||||
| # --------------------------- dataclasses -------------------------- | ||||
| 
 | ||||
| @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 (число элементов в массиве) | ||||
|     type_str: str = "" | ||||
|     size: Optional[int] = None                # общий размер (байты), если известен | ||||
|     children: List["MemberNode"] = field(default_factory=list) | ||||
|     # --- доп.поля --- | ||||
|     kind: Optional[str] = None                # 'array', 'union', ... | ||||
|     dims: Optional[List[int]] = None | ||||
| 
 | ||||
|     def is_date_struct(self) -> bool: | ||||
|         if not self.children: | ||||
| @ -44,51 +67,57 @@ class VariableNode: | ||||
|     type_str: str | ||||
|     size: Optional[int] | ||||
|     members: List[MemberNode] = field(default_factory=list) | ||||
|     # --- новые, но необязательные --- | ||||
|     kind: Optional[str] = None          # 'array' | ||||
|     count: Optional[int] = None         # size1 | ||||
|     # --- доп.поля --- | ||||
|     kind: Optional[str] = None                # 'array' | ||||
|     dims: Optional[List[int]] = None          # полный список размеров [size1, size2, ...] | ||||
| 
 | ||||
|     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 '.' | ||||
|     Читает XML и предоставляет методы: | ||||
|       - flattened(): плоский список всех путей. | ||||
|       - date_struct_candidates(): как раньше. | ||||
| 
 | ||||
|     Правила формирования путей: | ||||
|       * Структурные поля: '.' | ||||
|       * Поля через указатель на структуру: '->' | ||||
|       * Массивы: [index] (каждое измерение). | ||||
|     """ | ||||
|     # assumed primitive sizes (for STM/MCU: int=2) | ||||
| 
 | ||||
|     # предполагаемые размеры примитивов (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, | ||||
|         '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.timestamp: str = "" | ||||
|         self.variables: List[VariableNode] = [] | ||||
|         choose_type_map(0) | ||||
|         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')): | ||||
|         if txt.startswith(('0x', '0X')): | ||||
|             return int(txt, 16) | ||||
|         # если в строке есть буквы A-F → возможно hex | ||||
|         # если в строке есть A-F, попробуем hex | ||||
|         if any(c in 'abcdefABCDEF' for c in txt): | ||||
|             try: | ||||
|                 return int(txt, 16) | ||||
| @ -103,7 +132,7 @@ class VariablesXML: | ||||
|     def _is_pointer_to_struct(t: str) -> bool: | ||||
|         if not t: | ||||
|             return False | ||||
|         low = t.replace('\t',' ').replace('\n',' ') | ||||
|         low = t.replace('\t', ' ').replace('\n', ' ') | ||||
|         return 'struct ' in low and '*' in low | ||||
| 
 | ||||
|     @staticmethod | ||||
| @ -113,24 +142,34 @@ class VariablesXML: | ||||
|         low = t.strip() | ||||
|         return low.startswith('struct ') or low.startswith('union ') | ||||
| 
 | ||||
|     @staticmethod | ||||
|     def _is_union(t: str) -> bool: | ||||
|         if not t: | ||||
|             return False | ||||
|         low = t.strip() | ||||
|         return low.startswith('union ') | ||||
|      | ||||
|     @staticmethod | ||||
|     def _strip_array_suffix(t: str) -> str: | ||||
|         return t[:-2].strip() if t.endswith('[]') else t | ||||
|         t = t.strip() | ||||
|         while t.endswith('[]'): | ||||
|             t = t[:-2].strip() | ||||
|         return 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'): | ||||
|         for tok in ('volatile', 'const'): | ||||
|             base = base.replace(tok, '') | ||||
|         base = base.replace('*',' ') | ||||
|         base = base.replace('[',' ').replace(']',' ') | ||||
|         base = base.replace('*', ' ') | ||||
|         base = base.replace('[', ' ').replace(']', ' ') | ||||
|         base = ' '.join(base.split()).strip() | ||||
|         return self._PRIM_SIZE.get(base) | ||||
| 
 | ||||
|     # ------------------ XML read ------------------ | ||||
| 
 | ||||
|     def _parse(self): | ||||
|     def _parse(self) -> None: | ||||
|         try: | ||||
|             tree = ET.parse(self.path) | ||||
|             root = tree.getroot() | ||||
| @ -138,204 +177,235 @@ class VariablesXML: | ||||
|             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 '' | ||||
|             def parse_member(elem: ET.Element, base_offset=0) -> 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 | ||||
|                 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) | ||||
| 
 | ||||
|                 abs_offset = base_offset + offset | ||||
| 
 | ||||
| 
 | ||||
|                 # Собираем размеры, если есть | ||||
|                 dims: List[int] = [] | ||||
|                 i = 1 | ||||
|                 while True: | ||||
|                     size_key = f"size{i}" | ||||
|                     size_val = elem.get(size_key) | ||||
|                     if size_val is None: | ||||
|                         break | ||||
|                     parsed = self._parse_int_guess(size_val)  # предполагается твоя функция парсинга int | ||||
|                     if parsed is not None: | ||||
|                         dims.append(parsed) | ||||
|                     i += 1 | ||||
| 
 | ||||
|                 node = MemberNode( | ||||
|                     name=name, | ||||
|                     offset=abs_offset, | ||||
|                     type_str=t, | ||||
|                     size=size, | ||||
|                     kind=kind, | ||||
|                     dims=dims if dims else None, | ||||
|                 ) | ||||
| 
 | ||||
|                 # Для детей | ||||
|                 for ch in elem.findall('member'): | ||||
|                     node.children.append(parse_member(ch)) | ||||
|                     if kind == 'union': | ||||
|                         # Для union детей НЕ добавляем их offset, просто передаём abs_offset | ||||
|                         child = parse_member(ch, base_offset=abs_offset) | ||||
|                         child.offset = abs_offset  # выравниваем offset, игнорируем offset детей | ||||
|                     else: | ||||
|                         # Для struct/array суммируем offset нормально | ||||
|                         child = parse_member(ch, base_offset=abs_offset) | ||||
|                     node.children.append(child) | ||||
| 
 | ||||
|                 # Аналогично для pointee | ||||
|                 pointee_elem = elem.find('pointee') | ||||
|                 if pointee_elem is not None: | ||||
|                     for ch in pointee_elem.findall('member'): | ||||
|                         if kind == 'union': | ||||
|                             child = parse_member(ch, base_offset=abs_offset) | ||||
|                             child.offset = abs_offset | ||||
|                         else: | ||||
|                             child = parse_member(ch, base_offset=abs_offset) | ||||
|                         node.children.append(child) | ||||
|                     size_p = pointee_elem.get('size') | ||||
|                     if size_p: | ||||
|                         node.size = int(size_p, 16) | ||||
| 
 | ||||
|                 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 | ||||
|                 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) | ||||
| 
 | ||||
|                 dims: List[int] = [] | ||||
|                 i = 1 | ||||
|                 while True: | ||||
|                     key = f'size{i}' | ||||
|                     val = var.get(key) | ||||
|                     if val is None: | ||||
|                         break | ||||
|                     parsed = self._parse_int_guess(val) | ||||
|                     if parsed is not None: | ||||
|                         dims.append(parsed) | ||||
|                     i += 1 | ||||
| 
 | ||||
|                 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) | ||||
| 
 | ||||
|                 v = VariableNode( | ||||
|                     name=name, | ||||
|                     address=addr, | ||||
|                     type_str=t, | ||||
|                     size=size, | ||||
|                     members=members, | ||||
|                     kind=kind, | ||||
|                     dims=dims if dims else None, | ||||
|                 ) | ||||
|                 self.variables.append(v) | ||||
|         except FileNotFoundError: | ||||
|             self.variables = [] | ||||
|         except ET.ParseError: | ||||
|             self.variables = [] | ||||
| 
 | ||||
|     # ------------------ flatten (expanded) ------------------ | ||||
|     # ------------------ helpers для flattened --------------------- | ||||
| 
 | ||||
|     def flattened(self, | ||||
|                   max_array_elems: Optional[int] = None | ||||
|                   ) -> List[Dict[str, Any]]: | ||||
|     def _elem_size_bytes(self, total_size: Optional[int], dims: List[int], base_type: str, members: List[MemberNode]) -> int: | ||||
|         """Оценка размера одного *листового* элемента (последнего измерения). | ||||
|         Если total_size и dims все известны — берём size / prod(dims). | ||||
|         Иначе — пробуем примитивный размер; иначе 1. | ||||
|         (Не учитываем выравнивание структур; при необходимости можно расширить.) | ||||
|         """ | ||||
|         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). | ||||
|         if total_size is not None and dims: | ||||
|             prod = 1 | ||||
|             for d in dims: | ||||
|                 if d is None or d == 0: | ||||
|                     prod = None | ||||
|                     break | ||||
|                 prod *= d | ||||
|             if prod and prod > 0: | ||||
|                 return max(1, total_size // prod) | ||||
|         prim = self._guess_primitive_size(base_type) | ||||
|         if prim: | ||||
|             return prim | ||||
|         # Если структура и у неё есть size по детям? Пока fallback=1. | ||||
|         return 1 | ||||
| 
 | ||||
|     # ------------------ flattened ------------------ | ||||
| 
 | ||||
|     def flattened(self, max_array_elems: Optional[int] = None) -> List[Dict[str, Any]]: | ||||
|         """Возвращает плоский список всех путей (каждый путь = dict). | ||||
|         Включает промежуточные узлы массивов (var[0], var[0][0], ...). | ||||
|         """ | ||||
|         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 { | ||||
|         def mk(name: str, addr: Optional[int], type_str: str, size: Optional[int], kind: Optional[str], dims_for_node: Optional[List[int]]): | ||||
|             if 'Bender' in name: | ||||
|                 a=1 | ||||
|             out.append({ | ||||
|                 'name': name, | ||||
|                 'address': address, | ||||
|                 'address': addr, | ||||
|                 '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 '.' | ||||
|             """ | ||||
|                 'dims': dims_for_node[:] if dims_for_node else None, | ||||
|             }) | ||||
|              | ||||
|         def expand_members(prefix: str, base_addr: int, members: List[MemberNode], parent_is_ptr_struct: bool, parent_is_union: bool) -> None: | ||||
|             # Выбираем разделитель пути: '.' если обычный член, '->' если указатель на структуру | ||||
|             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)) | ||||
|                 path_m = f"{prefix}{join}{m.name}" if prefix else m.name | ||||
|                 is_union = m.kind == 'union' or parent_is_union | ||||
|                 if is_union: | ||||
|                     # Все поля union начинаются с одного адреса | ||||
|                     addr_m = base_addr | ||||
|                 else: | ||||
|                     addr_m = base_addr + m.offset | ||||
| 
 | ||||
|                 # array? | ||||
|                 if (m.kind == 'array') or m.type_str.endswith('[]'): | ||||
|                     count = m.count | ||||
|                     if count is None: | ||||
|                         count = 0 | ||||
|                     if count <= 0: | ||||
|                         continue | ||||
|                 dims = m.dims or [] | ||||
| 
 | ||||
|                 mk(path_m, addr_m, m.type_str, m.size, m.kind, dims) | ||||
| 
 | ||||
|                 if m.kind == 'array' and dims: | ||||
|                     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' | ||||
|                     elem_sz = m.size | ||||
| 
 | ||||
|                         # 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)) | ||||
|                     # Для массива внутри структуры: первый уровень — '.' для доступа, | ||||
|                     # внутри массива раскрываем по обычной логике с parent_is_ptr_struct=False | ||||
|                     expand_dims(path_m, addr_m, dims, base_t, m.children, elem_sz, parent_is_ptr_struct=False) | ||||
|                 else: | ||||
|                     if m.children: | ||||
|                         # Проверяем, является ли поле указателем на структуру | ||||
|                         is_ptr = self._is_pointer_to_struct(m.type_str) | ||||
|                         # Рекурсивно раскрываем дочерние поля, выбирая правильный разделитель | ||||
|                         expand_members(path_m, addr_m, m.children, is_ptr, is_union) | ||||
| 
 | ||||
|                         # 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) | ||||
|         def expand_dims(name: str, base_addr: int, dims: List[int], base_type: str, children: List[MemberNode], elem_size: int, parent_is_ptr_struct: bool) -> None: | ||||
|             prods: List[int] = [] | ||||
|             acc = 1 | ||||
|             for d in reversed(dims[1:]): | ||||
|                 acc *= (d if d else 1) | ||||
|                 prods.append(acc) | ||||
|             prods.reverse() | ||||
| 
 | ||||
|         # --- top-level variables --- | ||||
|             def rec(k: int, cur_name: str, cur_addr: int) -> None: | ||||
|                 if k == len(dims): | ||||
|                     # Листовой элемент массива | ||||
|                     mk(cur_name, cur_addr, base_type, elem_size, None, None) | ||||
|                     # Если элемент — структура или указатель на структуру, раскрываем вложения | ||||
|                     if children and self._is_struct_or_union(base_type): | ||||
|                         expand_members(cur_name, cur_addr, children, parent_is_ptr_struct=False, parent_is_union=self._is_union(base_type)) | ||||
|                     elif self._is_pointer_to_struct(base_type): | ||||
|                         expand_members(cur_name, cur_addr, children, parent_is_ptr_struct=True, parent_is_union=self._is_union(base_type)) | ||||
|                     return | ||||
| 
 | ||||
|                 dim_sz = dims[k] or 0 | ||||
|                 if max_array_elems is not None: | ||||
|                     dim_sz = min(dim_sz, max_array_elems) | ||||
| 
 | ||||
|                 stride = elem_size * prods[k] if k < len(prods) else elem_size | ||||
|                 if len(dims) > 2: | ||||
|                     a=1 | ||||
|                 for i in range(dim_sz): | ||||
|                     child_name = f"{cur_name}[{i}]" | ||||
|                     child_addr = (cur_addr + i * stride) if cur_addr is not None else None | ||||
|                     remaining = dims[k+1:] | ||||
|                     mk(child_name, child_addr, base_type + '[]' * len(remaining), stride if remaining else elem_size, 'array' if remaining else None, remaining) | ||||
|                     rec(k + 1, child_name, child_addr) | ||||
| 
 | ||||
|             rec(0, name, base_addr) | ||||
| 
 | ||||
| 
 | ||||
|         # --- цикл по топ‑левел переменным --- | ||||
|         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) | ||||
|             dims = v.dims or [] | ||||
|             mk(v.name, v.address, v.type_str, v.size, v.kind, dims) | ||||
|             if (v.kind == 'array' or v.type_str.endswith('[]')) and dims: | ||||
|                 base_t = self._strip_array_suffix(v.type_str) | ||||
|                 elem_sz = v.size | ||||
|                 expand_dims(v.name, v.address, dims, base_t, v.members, elem_sz, parent_is_ptr_struct=False) | ||||
|             else: | ||||
|                 if v.members: | ||||
|                     is_ptr = self._is_pointer_to_struct(v.type_str) | ||||
|                     is_union = self._is_union(v.type_str) | ||||
|                     expand_members(v.name, v.address, v.members, is_ptr, is_union) | ||||
| 
 | ||||
|         return out | ||||
| 
 | ||||
|     # -------------------- date candidates (as it was) -------------------- | ||||
|     # -------------------- date candidates (как раньше) ------------- | ||||
| 
 | ||||
|     def date_struct_candidates(self) -> List[Tuple[str,int]]: | ||||
|     def date_struct_candidates(self) -> List[Tuple[str, int]]: | ||||
|         cands = [] | ||||
|         for v in self.variables: | ||||
|             # top level (if all date fields are present) | ||||
| @ -349,90 +419,82 @@ class VariablesXML: | ||||
|         return cands | ||||
| 
 | ||||
| 
 | ||||
|     # ------------------------------------------------------------------ | ||||
|     # Построение иерархического дерева из flattened() | ||||
|     # ------------------------------------------------------------------ | ||||
|     def get_all_vars_data(self) -> List[Dict[str, Any]]: | ||||
|         """ | ||||
|         Возвращает вложенную структуру словарей с полными данными для всех переменных и их развернутых членов. | ||||
|         Каждый словарь представляет узел в иерархии и содержит: | ||||
|         'name' (полный путь), 'address', 'size', 'type', 'kind', 'count', и 'children' (если есть). | ||||
|         Логика определения родительского пути теперь использует `split_path` для анализа структуры пути. | ||||
|         Строит иерархию словарей из плоского списка переменных. | ||||
| 
 | ||||
|         Каждый узел = { | ||||
|             'name': <полный путь>, | ||||
|             'address': <адрес или None>, | ||||
|             'type': <тип>, | ||||
|             'size': <байты>, | ||||
|             'kind': <'array' | ...>, | ||||
|             'dims': [size1, size2, ...] или None, | ||||
|             'children': [...список дочерних узлов] | ||||
|         } | ||||
| 
 | ||||
|         Возвращает список корневых узлов (top-level переменных). | ||||
|         """ | ||||
|         flat_data = self.flattened(max_array_elems=None) | ||||
| 
 | ||||
|         root_nodes: List[Dict[str, Any]] = [] | ||||
|         all_nodes_map: Dict[str, Dict[str, Any]] = {} | ||||
| 
 | ||||
|         # Быстрое отображение имя -> узел (словарь с детьми) | ||||
|         all_nodes: Dict[str, Dict[str, Any]] = {} | ||||
|         for item in flat_data: | ||||
|             node_dict = {**item, 'children': []} | ||||
|             all_nodes_map[item['name']] = node_dict | ||||
|             node = dict(item) | ||||
|             node['children'] = [] | ||||
|             all_nodes[item['name']] = node | ||||
| 
 | ||||
|         # Вспомогательная функция для определения полного пути родителя с использованием 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] | ||||
|         def _parent_struct_split(path: str) -> Optional[str]: | ||||
|             # Ищем последний '.' или '->' для определения родителя | ||||
|             dot_idx = path.rfind('.') | ||||
|             arrow_idx = path.rfind('->') | ||||
|             cut_idx = max(dot_idx, arrow_idx) | ||||
|             if cut_idx == -1: | ||||
|                 return None | ||||
|             # '->' занимает 2 символа, нужно взять срез до начала '->' | ||||
|             if arrow_idx > dot_idx: | ||||
|                 return path[:arrow_idx] | ||||
|             else: | ||||
|                 return None # Корневой элемент без явного родителя | ||||
|                 return path[:dot_idx] | ||||
| 
 | ||||
|         # Основная логика 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) | ||||
|         def find_parent(path: str) -> Optional[str]: | ||||
|             """ | ||||
|             Возвращает полный путь родителя, учитывая '.', '->' и индексы [] в конце. | ||||
| 
 | ||||
|             Если путь заканчивается индексом [k], удаляет последний индекс и проверяет наличие родителя. | ||||
|             Иначе пытается найти последний сепаратор '.' или '->'. | ||||
|             """ | ||||
|             # Если есть trailing индекс в конце, убираем его | ||||
|             m = re.search(r'\[[0-9]+\]$', path) | ||||
|             if m: | ||||
|                 base = path[:m.start()]  # убираем последний [k] | ||||
|                 # Если базовый путь есть в узлах, считаем его родителем | ||||
|                 if base in all_nodes: | ||||
|                     return base | ||||
|                 # Иначе пытаемся найти родителя от базового пути | ||||
|                 return _parent_struct_split(base) | ||||
|             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 _parent_struct_split(path) | ||||
| 
 | ||||
|         return root_nodes | ||||
|         # Строим иерархию: parent -> children | ||||
|         roots: List[Dict[str, Any]] = [] | ||||
|         for full_name, node in all_nodes.items(): | ||||
|             parent_name = find_parent(full_name) | ||||
|             if parent_name and parent_name in all_nodes: | ||||
|                 all_nodes[parent_name]['children'].append(node) | ||||
|             else: | ||||
|                 roots.append(node) | ||||
| 
 | ||||
|         # Рекурсивно сортируем детей по имени для порядка | ||||
|         def sort_nodes(nodes: List[Dict[str, Any]]): | ||||
|             nodes.sort(key=lambda n: n['name']) | ||||
|             for n in nodes: | ||||
|                 if n['children']: | ||||
|                     sort_nodes(n['children']) | ||||
| 
 | ||||
|         sort_nodes(roots) | ||||
|         return roots | ||||
|  | ||||
| @ -12,10 +12,12 @@ from PyInstaller.utils.hooks import collect_data_files | ||||
| 
 | ||||
| # === Конфигурация === | ||||
| USE_NUITKA = True  # True — сборка через Nuitka, False — через PyInstaller | ||||
| MAIN_SCRIPT_NAME = "tms_debugvar_term" | ||||
| OUTPUT_NAME = "DebugVarTerminal" | ||||
| 
 | ||||
| 
 | ||||
| SRC_PATH = Path("./Src/") | ||||
| SCRIPT_PATH = SRC_PATH / "DebugVarEdit_GUI.py" | ||||
| OUTPUT_NAME = "DebugVarEdit" | ||||
| SCRIPT_PATH = SRC_PATH / (MAIN_SCRIPT_NAME + ".py") | ||||
| 
 | ||||
| DIST_PATH = Path("./").resolve() | ||||
| WORK_PATH = Path("./build_temp").resolve() | ||||
| @ -26,9 +28,9 @@ ICON_ICO_PATH = SRC_PATH / "icon.ico" | ||||
| TEMP_FOLDERS = [ | ||||
|     "build_temp", | ||||
|     "__pycache__", | ||||
|     "DebugVarEdit_GUI.build", | ||||
|     "DebugVarEdit_GUI.onefile-build", | ||||
|     "DebugVarEdit_GUI.dist" | ||||
|     MAIN_SCRIPT_NAME + ".build", | ||||
|     MAIN_SCRIPT_NAME + ".onefile-build", | ||||
|     MAIN_SCRIPT_NAME + ".dist" | ||||
| ] | ||||
| # === Пути к DLL и прочим зависимостям === | ||||
| LIBS = { | ||||
|  | ||||
| @ -221,3 +221,4 @@ class CsvLogger: | ||||
|             return (0, float(ts)) | ||||
|         except Exception: | ||||
|             return (1, str(ts)) | ||||
| 
 | ||||
|  | ||||
| @ -59,6 +59,47 @@ def split_path_tokens(path: str) -> List[str]: | ||||
|         tokens.append(token) | ||||
|     return tokens | ||||
| 
 | ||||
| def split_path_tokens_with_spans(path: str) -> List[Tuple[str, int, int]]: | ||||
|     """ | ||||
|     Возвращает список кортежей (токен, start_pos, end_pos) | ||||
|     Токены — так же, как в split_path_tokens, но с позициями в исходной строке. | ||||
|     """ | ||||
|     tokens = [] | ||||
|     i = 0 | ||||
|     L = len(path) | ||||
|     while i < L: | ||||
|         c = path[i] | ||||
|         start = i | ||||
|         # '->' | ||||
|         if c == '-' and i + 1 < L and path[i:i+2] == '->': | ||||
|             tokens.append(('->', start, start + 2)) | ||||
|             i += 2 | ||||
|             continue | ||||
|         if c == '.': | ||||
|             tokens.append(('.', start, start + 1)) | ||||
|             i += 1 | ||||
|             continue | ||||
|         if c == '[': | ||||
|             # захватим весь индекс с ']' | ||||
|             j = i | ||||
|             while j < L and path[j] != ']': | ||||
|                 j += 1 | ||||
|             if j < L and path[j] == ']': | ||||
|                 j += 1 | ||||
|             tokens.append((path[i:j], i, j)) | ||||
|             i = j | ||||
|             continue | ||||
|         # иначе - обычное имя (до точки, стрелки или скобок) | ||||
|         j = i | ||||
|         while j < L and path[j] not in ['.', '-', '[']: | ||||
|             if path[j] == '-' and j + 1 < L and path[j:j+2] == '->': | ||||
|                 break | ||||
|             j += 1 | ||||
|         tokens.append((path[i:j], i, j)) | ||||
|         i = j | ||||
|     # фильтруем из списка токены-разделители '.' и '->' чтобы оставить только логические части | ||||
|     filtered = [t for t in tokens if t[0] not in ['.', '->']] | ||||
|     return filtered | ||||
| 
 | ||||
| def canonical_key(path: str) -> str: | ||||
|     """ | ||||
| @ -137,28 +178,19 @@ class PathHints: | ||||
|         self._paths.append(full_path) | ||||
|         self._types[full_path] = type_str | ||||
| 
 | ||||
|         toks = split_path_tokens(full_path) | ||||
|         if not toks: | ||||
|         tokens_spans = split_path_tokens_with_spans(full_path) | ||||
|         if not tokens_spans: | ||||
|             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 | ||||
| 
 | ||||
|             # Если узел уже есть | ||||
|         for i, (tok, start, end) in enumerate(tokens_spans): | ||||
|             cur_full = full_path[:end]  # подстрока с начала до конца токена включительно | ||||
|              | ||||
|             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 | ||||
| 
 | ||||
|  | ||||
| @ -339,7 +339,7 @@ class LowLevelSelectorWidget(QWidget): | ||||
|             name = var['ptr_type'] | ||||
|         elif isinstance(var.get('ptr_type_name'), str): | ||||
|             name = var['ptr_type_name'] | ||||
|         elif isinstance(var.get('pt_type'), str): | ||||
|         elif isinstance(var.get('pt_type'), str) and 'pt_' in var.get('pt_type'): | ||||
|             name = var['pt_type'].replace('pt_','') | ||||
|         elif isinstance(var.get('ptr_type'), int): | ||||
|             name = PT_ENUM_NAME_FROM_VAL.get(var['ptr_type'], 'unknown') | ||||
| @ -456,7 +456,7 @@ class LowLevelSelectorWidget(QWidget): | ||||
|             return False | ||||
| 
 | ||||
|         # 2. Обновляем отображение в таблице | ||||
|         self.var_table.populate(self._all_available_vars, {}, self._on_var_table_changed) | ||||
|         #self.var_table.populate(self._all_available_vars, {}, self._on_var_table_changed) | ||||
|         return True | ||||
| 
 | ||||
|     # --------------- Address mapping / type mapping helpers --------------- | ||||
|  | ||||
| @ -1,7 +1,8 @@ | ||||
| from PySide2 import QtCore, QtWidgets, QtSerialPort | ||||
| from PySide2 import QtCore, QtWidgets, QtSerialPort, QtGui | ||||
| from tms_debugvar_lowlevel import LowLevelSelectorWidget | ||||
| import datetime | ||||
| import time | ||||
| import os | ||||
| from csv_logger import CsvLogger | ||||
| # ------------------------------- Константы протокола ------------------------ | ||||
| WATCH_SERVICE_BIT = 0x8000 | ||||
| @ -44,6 +45,19 @@ def crc16_ibm(data: bytes, *, init=0xFFFF) -> int: | ||||
|                 crc >>= 1 | ||||
|     return crc & 0xFFFF | ||||
| 
 | ||||
| def is_frozen(): | ||||
|     # Для Nuitka --onefile | ||||
|     return getattr(sys, 'frozen', False) | ||||
| 
 | ||||
| 
 | ||||
| def get_base_path(): | ||||
|     if is_frozen(): | ||||
|         # В Nuitka onefile распаковывается в папку с самим exe во временной директории | ||||
|         return os.path.dirname(sys.executable) | ||||
|     else: | ||||
|         # Режим разработки | ||||
|         return os.path.dirname(os.path.abspath(__file__)) | ||||
|      | ||||
| def _decode_debug_status(status: int) -> str: | ||||
|         """Преобразует код статуса прошивки в строку. | ||||
|         Возвращает 'OK' или перечисление битов через '|'. | ||||
| @ -97,9 +111,6 @@ class Spoiler(QtWidgets.QWidget): | ||||
|         self._ani_content.setDuration(animationDuration) | ||||
|         self._ani_content.setEasingCurve(QtCore.QEasingCurve.InOutCubic) | ||||
| 
 | ||||
|         # Следим за шагами анимации → обновляем родителя | ||||
|         self._ani_content.valueChanged.connect(self._adjust_parent_size) | ||||
| 
 | ||||
|         # --- Layout --- | ||||
|         self.mainLayout = QtWidgets.QGridLayout(self) | ||||
|         self.mainLayout.setVerticalSpacing(0) | ||||
| @ -120,13 +131,6 @@ class Spoiler(QtWidgets.QWidget): | ||||
|     def getState(self): | ||||
|         return self.state | ||||
| 
 | ||||
|     def _adjust_parent_size(self, *_): | ||||
|         top = self.window() | ||||
|         if top: | ||||
|             size = top.size() | ||||
|             size.setHeight(top.sizeHint().height())  # берём новую высоту | ||||
|             top.resize(size)  # ширина остаётся прежней | ||||
| 
 | ||||
|     def _on_toggled(self, checked: bool): | ||||
|         self.state = checked | ||||
|         self.toggleButton.setArrowType(QtCore.Qt.DownArrow if checked else QtCore.Qt.RightArrow) | ||||
| @ -135,12 +139,6 @@ class Spoiler(QtWidgets.QWidget): | ||||
|         self._ani_content.stop() | ||||
|         self._ani_content.setStartValue(self.contentArea.maximumHeight()) | ||||
|         self._ani_content.setEndValue(contentHeight if checked else 0) | ||||
| 
 | ||||
|         # --- Фиксируем ширину на время анимации --- | ||||
|         w = self.width() | ||||
|         self.setFixedWidth(w) | ||||
|         self._ani_content.finished.connect(lambda: self.setMaximumWidth(16777215))  # сброс фикса | ||||
| 
 | ||||
|         self._ani_content.start() | ||||
| 
 | ||||
| 
 | ||||
| @ -420,9 +418,8 @@ class DebugTerminalWidget(QtWidgets.QWidget): | ||||
|         self.chk_hex_index.stateChanged.connect(self._toggle_index_base) | ||||
| 
 | ||||
|         # LowLevel (новые и переделанные) | ||||
|         self.ll_selector.variablePrepared.connect(self._on_ll_variable_prepared) | ||||
|         self.ll_selector.xmlLoaded.connect(lambda p: self._log(f"[LL] XML loaded: {p}")) | ||||
|         self.ll_selector.btn_read_once.clicked.connect(self.request_lowlevel_once) | ||||
|         self.ll_selector.btn_read_once.clicked.connect(self._start_ll_cycle) | ||||
|         self.ll_selector.btn_start_polling.clicked.connect(self._toggle_ll_polling) | ||||
| 
 | ||||
|         # --- CSV Logging --- | ||||
| @ -681,7 +678,7 @@ class DebugTerminalWidget(QtWidgets.QWidget): | ||||
|             # !!! Раньше тут было `return`, его убираем | ||||
| 
 | ||||
|         # Если идёт LL polling — переходим сразу к следующей переменной | ||||
|         if self._ll_polling and (self._ll_poll_index < len(self._ll_polling_variables)): | ||||
|         if self._ll_polling: | ||||
|             self._process_next_ll_variable_in_cycle() | ||||
|             return | ||||
| 
 | ||||
| @ -897,13 +894,20 @@ class DebugTerminalWidget(QtWidgets.QWidget): | ||||
|         crc_lo, crc_hi = frame[crc_pos], frame[crc_pos+1] | ||||
| 
 | ||||
|         self._check_crc(payload, crc_lo, crc_hi) | ||||
|          | ||||
| 
 | ||||
|         status = payload[2] | ||||
|         addr2, addr1, addr0 = payload[3], payload[4], payload[5] | ||||
|         addr24 = (addr2 << 16) | (addr1 << 8) | addr0 | ||||
| 
 | ||||
|         status_desc = _decode_debug_status(status) | ||||
| 
 | ||||
|         if not success: | ||||
|             # Ошибка — в ответе нет ReturnType и данных, только статус | ||||
|             self._log(f"[LL] ERROR addr=0x{addr24:06X} status=0x{status:02X} ({status_desc})") | ||||
|             self.llValueRead.emit(addr24, status, None, None, None) | ||||
|             return None | ||||
| 
 | ||||
|         # Если success == True, продолжаем парсить ReturnType и данные | ||||
|         return_type = payload[6] | ||||
|         data_hi, data_lo = payload[7], payload[8] | ||||
|         raw16 = (data_hi << 8) | data_lo | ||||
| @ -922,8 +926,7 @@ class DebugTerminalWidget(QtWidgets.QWidget): | ||||
|             scale = self.iq_scaling.get(frac_bits, 1.0 / (1 << frac_bits)) # 1 / 2^N | ||||
| 
 | ||||
|         scaled = float(value_int) / scale | ||||
|          | ||||
|          | ||||
| 
 | ||||
|         self.llValueRead.emit(addr24, status, return_type, value_int, scaled) | ||||
| 
 | ||||
|         var_name = None | ||||
| @ -935,7 +938,7 @@ class DebugTerminalWidget(QtWidgets.QWidget): | ||||
| 
 | ||||
|         self._log(f"[LL] OK addr=0x{addr24:06X} type=0x{return_type:02X} raw={value_int} scaled={scaled:.6g}") | ||||
| 
 | ||||
|         current_time = time.time() # Получаем текущее время | ||||
|         current_time = time.time() | ||||
|         self.csv_logger.set_value(current_time, var_name, display_val) | ||||
| 
 | ||||
| 
 | ||||
| @ -1030,6 +1033,7 @@ class DebugTerminalWidget(QtWidgets.QWidget): | ||||
|             self.btn_poll.setText("Stop Polling") | ||||
|             self.set_status("Idle", "idle") | ||||
|             self._log(f"[POLL] Started interval={interval}ms") | ||||
| 
 | ||||
|         self._set_ui_busy(False) # Обновить доступность кнопок | ||||
| 
 | ||||
|     def _on_poll_timeout(self):         | ||||
| @ -1063,6 +1067,7 @@ class DebugTerminalWidget(QtWidgets.QWidget): | ||||
|              | ||||
|             # Immediately kick off the first variable read of the first cycle | ||||
|             self._start_ll_cycle() | ||||
|         self._set_ui_busy(False) # Обновить доступность кнопок | ||||
| 
 | ||||
| 
 | ||||
|     def _on_ll_poll_timeout(self): | ||||
| @ -1096,14 +1101,19 @@ class DebugTerminalWidget(QtWidgets.QWidget): | ||||
|             self._ll_poll_index += 1 | ||||
|             frame = self._build_lowlevel_request(var_info) | ||||
|             # --- НОВОЕ: Передаем var_info в метаданные транзакции для LL polling --- | ||||
|             meta = {'lowlevel': True, 'll_polling': True, 'll_var_info': var_info}  | ||||
|             self.set_status(f"Polling LL: {var_info.get('name')}", "values") | ||||
|             meta = {'lowlevel': True, 'll_polling': True, 'll_var_info': var_info}              | ||||
|             # Получаем адрес переменной, предполагаем что ключ называется 'addr' или 'address' | ||||
|             addr = var_info.get('addr') or var_info.get('address') | ||||
|             if addr is not None: | ||||
|                 addr_str = f"0x{addr:06X}" | ||||
|             else: | ||||
|                 addr_str = "addr unknown" | ||||
| 
 | ||||
|             self.set_status(f"Polling LL: {addr_str} {var_info.get('name')}", "values") | ||||
|             self._enqueue_raw(frame, meta) | ||||
|         else: | ||||
|             # Цикл завершен, перезапускаем таймер для следующего полного цикла | ||||
|             self._ll_poll_index = 0 | ||||
|             self._ll_poll_timer.start(self.ll_selector.spin_interval.value()) | ||||
|             self.set_status("LL polling cycle done, waiting...", "idle") | ||||
|             self.ll_selector._populate_var_table() | ||||
|     # ------------------------------ HELPERS -------------------------------- | ||||
|     def _toggle_index_base(self, st): | ||||
|         # ... (код без изменений) | ||||
| @ -1180,7 +1190,7 @@ class DebugTerminalWidget(QtWidgets.QWidget): | ||||
|         self._csv_logging_active = True | ||||
|         self.btn_start_csv_logging.setEnabled(False) | ||||
|         self.btn_stop_csv_logging.setEnabled(True) | ||||
|         self.set_status("CSV Logging ACTIVE", "values") | ||||
|         self.set_status("CSV Logging ACTIVATED", "service") | ||||
|         self._log("[CSV] Запись данных в CSV началась.") | ||||
| 
 | ||||
| 
 | ||||
| @ -1189,7 +1199,7 @@ class DebugTerminalWidget(QtWidgets.QWidget): | ||||
|         self._csv_logging_active = False | ||||
|         self.btn_start_csv_logging.setEnabled(True) | ||||
|         self.btn_stop_csv_logging.setEnabled(False) | ||||
|         self.set_status("CSV Logging STOPPED", "idle") | ||||
|         self.set_status("CSV Logging STOPPED", "service") | ||||
|         self._log("[CSV] Запись данных в CSV остановлена.") | ||||
| 
 | ||||
|     def _save_csv_data(self): | ||||
| @ -1199,7 +1209,7 @@ class DebugTerminalWidget(QtWidgets.QWidget): | ||||
|             self.set_status("Stop logging first", "error") | ||||
|             return | ||||
|         self.csv_logger.write_to_csv() | ||||
|         self.set_status("CSV data saved", "idle") | ||||
|         self.set_status("CSV data saved", "service") | ||||
|          | ||||
|     def _log(self, msg: str): | ||||
|         # ... (код без изменений) | ||||
| @ -1235,6 +1245,10 @@ class DebugTerminalWidget(QtWidgets.QWidget): | ||||
| class _DemoWindow(QtWidgets.QMainWindow): | ||||
|     def __init__(self): | ||||
|         super().__init__() | ||||
|         base_path = get_base_path() | ||||
|         icon_path = os.path.join(base_path, "icon.ico") | ||||
|         if os.path.exists(icon_path): | ||||
|             self.setWindowIcon(QtGui.QIcon(icon_path))         | ||||
|         self.setWindowTitle("DebugVar Terminal") | ||||
|         self.term = DebugTerminalWidget(self) | ||||
|         self.setCentralWidget(self.term) | ||||
|  | ||||
| @ -206,7 +206,7 @@ class VariableSelectorDialog(QDialog): | ||||
|                     'enable': 'true',  | ||||
|                     'shortname': name,  | ||||
|                     'pt_type': '',  | ||||
|                     'iq_type': '', | ||||
|                     'iq_type': 't_iq_none', | ||||
|                     'return_type': 't_iq_none',  | ||||
|                     'file': file_val, | ||||
|                     'extern': str(extern_val).lower() if extern_val else 'false', | ||||
|  | ||||
| @ -64,7 +64,8 @@ class SetSizeDialog(QDialog): | ||||
|     """ | ||||
|     Диалоговое окно для выбора числового значения (размера). | ||||
|     """ | ||||
|     def __init__(self, parent=None, initial_value=10, min_value=1, max_value=50, title="Укажите размер короткого имени"): | ||||
|     def __init__(self, parent=None, initial_value=10, min_value=1, max_value=50, title="Укажите размер короткого имени", | ||||
|                  label_text="Количество символов:"): | ||||
|         super().__init__(parent) | ||||
|         self.setWindowTitle(title) | ||||
|         self.setFixedSize(320, 120)  # Задаем фиксированный размер для аккуратного вида | ||||
| @ -74,7 +75,7 @@ class SetSizeDialog(QDialog): | ||||
| 
 | ||||
|         # Макет для ввода значения | ||||
|         input_layout = QHBoxLayout() | ||||
|         label = QLabel("Количество символов:", self) | ||||
|         label = QLabel(label_text, self) | ||||
|          | ||||
|         self.spin_box = QSpinBox(self) | ||||
|         self.spin_box.setRange(min_value, max_value)  # Устанавливаем диапазон допустимых значений | ||||
| @ -155,6 +156,9 @@ class VariableTableWidget(QTableWidget): | ||||
|         shortsize = self.settings.value("shortname_size", True, type=int) | ||||
|         self._shortname_size = shortsize | ||||
| 
 | ||||
|         if(self._show_value): | ||||
|             self._shortname_size = 3 | ||||
| 
 | ||||
|         self.type_options = list(dict.fromkeys(type_map.values())) | ||||
|         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)] | ||||
| @ -263,13 +267,27 @@ class VariableTableWidget(QTableWidget): | ||||
| 
 | ||||
|             # Последний столбец | ||||
|             if self._show_value: | ||||
|                 val = var.get('value', '') | ||||
|                 if val is None: | ||||
|                     val = '' | ||||
|                 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) | ||||
|                 if self._show_value: | ||||
|                     val = var.get('value', '') | ||||
|                     if val is None: | ||||
|                         val = '' | ||||
|                     else: | ||||
|                         try: | ||||
|                             f_val = float(val) | ||||
|                             # Форматируем число с учетом self._shortname_size | ||||
|                             if f_val.is_integer(): | ||||
|                                 val = str(int(f_val)) | ||||
|                             else: | ||||
|                                 precision = getattr(self, "_shortname_size", 3)  # по умолчанию 3 | ||||
|                                 val = f"{f_val:.{precision}f}" | ||||
|                         except ValueError: | ||||
|                             # Если значение не число (строка и т.п.), оставляем как есть | ||||
|                             val = str(val) | ||||
| 
 | ||||
|                     val_edit = QLineEdit(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) | ||||
| @ -309,9 +327,10 @@ class VariableTableWidget(QTableWidget): | ||||
|             if not found: | ||||
|                 color = error_color | ||||
|                 tooltip = tooltip_missing | ||||
|             elif long_shortname: | ||||
|                 color = warning_color | ||||
|                 tooltip = tooltip_shortname | ||||
|             elif long_shortname:                 | ||||
|                 if not self._show_value: | ||||
|                     color = warning_color | ||||
|                     tooltip = tooltip_shortname | ||||
| 
 | ||||
|             self.highlight_row(row, color, tooltip) | ||||
|             t4 = time.time() | ||||
| @ -365,10 +384,15 @@ class VariableTableWidget(QTableWidget): | ||||
|                 self.update_comboboxes({rows.ret_type: self._ret_type_filter}) | ||||
|                  | ||||
|         elif logicalIndex == rows.short_name: | ||||
|             dlg = SetSizeDialog(self) | ||||
|             if self._show_value: | ||||
|                 dlg = SetSizeDialog(self, title="Укажите точность", label_text="Кол-во знаков после запятой", initial_value=3) | ||||
|             else: | ||||
|                 dlg = SetSizeDialog(self) | ||||
| 
 | ||||
|             if dlg.exec_(): | ||||
|                 self._shortname_size = dlg.get_selected_size()    | ||||
|                 self.settings.setValue("shortname_size", self._shortname_size)              | ||||
|                 if not self._show_value: | ||||
|                     self.settings.setValue("shortname_size", self._shortname_size)              | ||||
|                 self.check() | ||||
| 
 | ||||
| 
 | ||||
|  | ||||
| @ -1,12 +1,14 @@ | ||||
| # 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 | ||||
| # pyinstaller --onefile --distpath ./parse_xml --workpath ./parse_xml/build --specpath ./build parse_xml/Src/parse_xml.py | ||||
| # python -m nuitka --standalone --onefile --output-dir=./parse_xml 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]") | ||||
|     print("Usage: python parse_xml.exe <input.xml> <info.txt> [output.xml]") | ||||
|     sys.exit(1) | ||||
| 
 | ||||
| input_path = sys.argv[1] | ||||
| @ -159,75 +161,6 @@ def parse_offset(offset_text): | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 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 | ||||
| @ -244,11 +177,98 @@ def get_base_type_die(array_die): | ||||
|             return next_die | ||||
|     return current_die | ||||
| 
 | ||||
| def get_array_dimensions(array_die): | ||||
|     dims = [] | ||||
|     # Итерируем по всем DIE с тегом DW_TAG_subrange_type, потомки текущего массива | ||||
|     for child in array_die.findall("die"): | ||||
|         if child.findtext("tag") != "DW_TAG_subrange_type": | ||||
|             continue | ||||
| 
 | ||||
|         dim_size = None | ||||
|         ub_attr = get_attr(child, "DW_AT_upper_bound") | ||||
|         if ub_attr is not None: | ||||
|             # Попробуем разные варианты получить значение upper_bound | ||||
|             # 1) value/const | ||||
|             val_const = ub_attr.find("const") | ||||
|             if val_const is not None: | ||||
|                 try: | ||||
|                     dim_size = int(val_const.text, 0) + 1 | ||||
|                     #print(f"[DEBUG] Found DW_AT_upper_bound const: {val_const.text}, size={dim_size}") | ||||
|                 except Exception as e: | ||||
|                     a=1#print(f"[WARN] Error parsing upper_bound const: {e}") | ||||
|             else: | ||||
|                 # 2) value/block (DW_OP_constu / DW_OP_plus_uconst, etc.) | ||||
|                 val_block = ub_attr.find("block") | ||||
|                 if val_block is not None: | ||||
|                     block_text = val_block.text | ||||
|                     # Можно попытаться парсить DWARF expr (например DW_OP_plus_uconst 7) | ||||
|                     if block_text and "DW_OP_plus_uconst" in block_text: | ||||
|                         try: | ||||
|                             parts = block_text.split() | ||||
|                             val = int(parts[-1], 0) | ||||
|                             dim_size = val + 1 | ||||
|                             #print(f"[DEBUG] Parsed upper_bound block: {val} + 1 = {dim_size}") | ||||
|                         except Exception as e: | ||||
|                             a=1#print(f"[WARN] Error parsing upper_bound block: {e}") | ||||
|                     else: | ||||
|                         a=1#print(f"[WARN] Unexpected DW_AT_upper_bound block content: {block_text}") | ||||
|                 else: | ||||
|                     a=1#print(f"[WARN] DW_AT_upper_bound has no const or block value") | ||||
|          | ||||
|         if dim_size is None: | ||||
|             # fallback по DW_AT_count — редко встречается | ||||
|             ct_attr = get_attr(child, "DW_AT_count") | ||||
|             if ct_attr is not None: | ||||
|                 val_const = ct_attr.find("value/const") | ||||
|                 if val_const is not None: | ||||
|                     try: | ||||
|                         dim_size = int(val_const.text, 0) | ||||
|                         #print(f"[DEBUG] Found DW_AT_count: {dim_size}") | ||||
|                     except Exception as e: | ||||
|                         a=1#print(f"[WARN] Error parsing DW_AT_count const: {e}") | ||||
| 
 | ||||
|         if dim_size is None: | ||||
|             print("[DEBUG] No dimension size found for this subrange, defaulting to 0") | ||||
|             dim_size = 0 | ||||
| 
 | ||||
|         dims.append(dim_size) | ||||
| 
 | ||||
|     # Если не нашли измерений — пытаемся вычислить размер массива по общему размеру | ||||
|     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) | ||||
|                 #print(f"[DEBUG] Fallback: arr_size={arr_size}, elem_size={elem_size}") | ||||
| 
 | ||||
|         if arr_size is not None and elem_size: | ||||
|             dim_calc = arr_size // elem_size | ||||
|             dims.append(dim_calc) | ||||
|             #print(f"[DEBUG] Calculated dimension size from total size: {dim_calc}") | ||||
|         else: | ||||
|             dims.append(0) | ||||
|             print("[DEBUG] Could not calculate dimension size, set 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)) | ||||
| 
 | ||||
|     #print(f"[DEBUG] Array dimensions: {dims}") | ||||
|     return dims | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 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 | ||||
| @ -259,10 +279,10 @@ def handle_array_type(member_elem, resolved_type, offset=0): | ||||
|             base_size = get_die_size(base_die) | ||||
|         else: | ||||
|             base_name = get_type_name(base_die.attrib.get("id", "")) | ||||
|     #print(f"[DEBUG] Base type name: {base_name}, base size: {base_size}") | ||||
| 
 | ||||
|     member_elem.set("type", base_name + "[]" * len(dims)) | ||||
| 
 | ||||
|     # Вычисляем общий размер массива — произведение размеров * размер базового элемента | ||||
|     if base_size is None: | ||||
|         base_size = 0 | ||||
| 
 | ||||
| @ -270,31 +290,34 @@ def handle_array_type(member_elem, resolved_type, offset=0): | ||||
|     for d in dims: | ||||
|         if d == 0: | ||||
|             total_elements = 0 | ||||
|             print(f"[WARN] Dimension size is zero, setting total elements to 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)) | ||||
|         member_elem.set("size", str(base_size if base_size is not None else 1)) | ||||
|     else: | ||||
|         # fallback: если не удалось, можно попробовать get_die_size | ||||
|         arr_size = get_die_size(resolved_type) | ||||
|         if arr_size: | ||||
|             member_elem.set("size", str(arr_size)) | ||||
|             #print(f"[DEBUG] Used fallback size from resolved_type: {arr_size}") | ||||
|         else: | ||||
|             print(f"[WARN] Could not determine total size for array") | ||||
| 
 | ||||
|     # Записываем размеры измерений size1, size2 ... | ||||
|     for i, dim in enumerate(dims, 1): | ||||
|         member_elem.set(f"size{i}", str(dim)) | ||||
|         #print(f"[DEBUG] Setting size{i} = {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) | ||||
| @ -330,6 +353,20 @@ def add_members_recursive(parent_elem, struct_die, base_offset=0): | ||||
|             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) | ||||
|             elif tag == "DW_TAG_pointer_type": | ||||
|                 # Проверяем тип, на который указывает указатель | ||||
|                 pointee_ref = get_attr(resolved_type, "DW_AT_type") | ||||
|                 if pointee_ref is not None and pointee_ref.find("ref") is not None: | ||||
|                     pointee_id = pointee_ref.find("ref").attrib.get("idref") | ||||
|                     pointee_die = resolve_type_die(pointee_id) | ||||
|                     if pointee_die is not None: | ||||
|                         pointee_tag = pointee_die.findtext("tag") | ||||
|                         if pointee_tag in ("DW_TAG_structure_type", "DW_TAG_union_type"): | ||||
|                             # Добавляем подэлементы для структуры, на которую указывает указатель | ||||
|                             pointer_elem = ET.SubElement(member_elem, "pointee", type=get_type_name(pointee_id)) | ||||
|                             add_members_recursive(pointer_elem, pointee_die, 0) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| output_root = ET.Element("variables") | ||||
| for die in root.iter("die"): | ||||
|  | ||||
										
											Binary file not shown.
										
									
								
							
		Loading…
	
		Reference in New Issue
	
	Block a user