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