debugVarTool/Src/allvars_xml_parser.py
Razvalyaev 502046091c опять кууууча всего:
базово доделаны терминалки до более менее итогового состояния
2025-07-23 17:13:28 +03:00

501 lines
21 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
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 re
import xml.etree.ElementTree as ET
from dataclasses import dataclass, field
from typing import List, Dict, Optional, Tuple, Any
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', ...
dims: Optional[List[int]] = None
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'
dims: Optional[List[int]] = None # полный список размеров [size1, size2, ...]
def base_address_hex(self) -> str:
return f"0x{self.address:06X}"
# --------------------------- класс парсера -----------------------
class VariablesXML:
"""
Читает XML и предоставляет методы:
- flattened(): плоский список всех путей.
- date_struct_candidates(): как раньше.
Правила формирования путей:
* Структурные поля: '.'
* Поля через указатель на структуру: '->'
* Массивы: [index] (каждое измерение).
"""
# предполагаемые размеры примитивов (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()
# ------------------ утилиты ------------------
@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 _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:
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'):
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) -> None:
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: 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
kind = elem.get('kind')
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'):
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
kind = var.get('kind')
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')]
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 = []
# ------------------ helpers для flattened ---------------------
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.
(Не учитываем выравнивание структур; при необходимости можно расширить.)
"""
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 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': addr,
'type': type_str,
'size': size,
'kind': kind,
'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}{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
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)
elem_sz = m.size
# Для массива внутри структуры: первый уровень — '.' для доступа,
# внутри массива раскрываем по обычной логике с 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)
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()
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:
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 (как раньше) -------------
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
# ------------------------------------------------------------------
# Построение иерархического дерева из flattened()
# ------------------------------------------------------------------
def get_all_vars_data(self) -> List[Dict[str, Any]]:
"""
Строит иерархию словарей из плоского списка переменных.
Каждый узел = {
'name': <полный путь>,
'address': <адрес или None>,
'type': <тип>,
'size': <байты>,
'kind': <'array' | ...>,
'dims': [size1, size2, ...] или None,
'children': [...список дочерних узлов]
}
Возвращает список корневых узлов (top-level переменных).
"""
flat_data = self.flattened(max_array_elems=None)
# Быстрое отображение имя -> узел (словарь с детьми)
all_nodes: Dict[str, Dict[str, Any]] = {}
for item in flat_data:
node = dict(item)
node['children'] = []
all_nodes[item['name']] = node
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 path[:dot_idx]
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:
# Если нет индекса, просто ищем последний разделитель
return _parent_struct_split(path)
# Строим иерархию: 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