mirror of
https://github.com/NohamR/Reclass.git
synced 2026-05-10 19:59:21 +00:00
Fix 7 data bugs: hex endian, signed hex, array span, UTF-8 positions, array undo, offset sign
This commit is contained in:
@@ -18,6 +18,7 @@ struct ComposeState {
|
|||||||
int currentLine = 0;
|
int currentLine = 0;
|
||||||
int typeW = kColType; // global type column width (fallback)
|
int typeW = kColType; // global type column width (fallback)
|
||||||
int nameW = kColName; // global name column width (fallback)
|
int nameW = kColName; // global name column width (fallback)
|
||||||
|
bool baseEmitted = false; // only first root struct shows base address
|
||||||
|
|
||||||
// Precomputed for O(1) lookups
|
// Precomputed for O(1) lookups
|
||||||
QHash<uint64_t, QVector<int>> childMap;
|
QHash<uint64_t, QVector<int>> childMap;
|
||||||
@@ -206,7 +207,8 @@ void composeParent(ComposeState& state, const NodeTree& tree,
|
|||||||
lm.foldCollapsed = node.collapsed;
|
lm.foldCollapsed = node.collapsed;
|
||||||
lm.foldLevel = computeFoldLevel(depth, true);
|
lm.foldLevel = computeFoldLevel(depth, true);
|
||||||
lm.markerMask = (1u << M_STRUCT_BG);
|
lm.markerMask = (1u << M_STRUCT_BG);
|
||||||
lm.isRootHeader = (node.parentId == 0 && node.kind == NodeKind::Struct);
|
lm.isRootHeader = (node.parentId == 0 && node.kind == NodeKind::Struct && !state.baseEmitted);
|
||||||
|
if (lm.isRootHeader) state.baseEmitted = true;
|
||||||
|
|
||||||
QString headerText;
|
QString headerText;
|
||||||
if (node.kind == NodeKind::Array) {
|
if (node.kind == NodeKind::Array) {
|
||||||
@@ -350,7 +352,7 @@ ComposeResult compose(const NodeTree& tree, const Provider& prov) {
|
|||||||
}
|
}
|
||||||
maxTypeLen = qMax(maxTypeLen, typeName.size());
|
maxTypeLen = qMax(maxTypeLen, typeName.size());
|
||||||
}
|
}
|
||||||
state.typeW = qBound(kMinTypeW, maxTypeLen + 1, kMaxTypeW);
|
state.typeW = qBound(kMinTypeW, maxTypeLen, kMaxTypeW);
|
||||||
|
|
||||||
// Compute effective name column width from longest name
|
// Compute effective name column width from longest name
|
||||||
int maxNameLen = kMinNameW;
|
int maxNameLen = kMinNameW;
|
||||||
@@ -361,7 +363,7 @@ ComposeResult compose(const NodeTree& tree, const Provider& prov) {
|
|||||||
if (node.kind == NodeKind::Struct || node.kind == NodeKind::Array) continue;
|
if (node.kind == NodeKind::Struct || node.kind == NodeKind::Array) continue;
|
||||||
maxNameLen = qMax(maxNameLen, node.name.size());
|
maxNameLen = qMax(maxNameLen, node.name.size());
|
||||||
}
|
}
|
||||||
state.nameW = qBound(kMinNameW, maxNameLen + 1, kMaxNameW);
|
state.nameW = qBound(kMinNameW, maxNameLen, kMaxNameW);
|
||||||
|
|
||||||
// Pre-compute per-scope widths (each container gets widths based on direct children only)
|
// Pre-compute per-scope widths (each container gets widths based on direct children only)
|
||||||
for (int i = 0; i < tree.nodes.size(); i++) {
|
for (int i = 0; i < tree.nodes.size(); i++) {
|
||||||
@@ -375,24 +377,43 @@ ComposeResult compose(const NodeTree& tree, const Provider& prov) {
|
|||||||
for (int childIdx : state.childMap.value(container.id)) {
|
for (int childIdx : state.childMap.value(container.id)) {
|
||||||
const Node& child = tree.nodes[childIdx];
|
const Node& child = tree.nodes[childIdx];
|
||||||
|
|
||||||
|
// Skip containers - their headers don't use columnar layout
|
||||||
|
if (child.kind == NodeKind::Struct || child.kind == NodeKind::Array)
|
||||||
|
continue;
|
||||||
|
|
||||||
// Type width
|
// Type width
|
||||||
QString childTypeName;
|
QString childTypeName = fmt::typeNameRaw(child.kind);
|
||||||
if (child.kind == NodeKind::Array) {
|
|
||||||
childTypeName = fmt::arrayTypeName(child.elementKind, child.arrayLen);
|
|
||||||
} else {
|
|
||||||
childTypeName = fmt::typeNameRaw(child.kind);
|
|
||||||
}
|
|
||||||
scopeMaxType = qMax(scopeMaxType, childTypeName.size());
|
scopeMaxType = qMax(scopeMaxType, childTypeName.size());
|
||||||
|
|
||||||
// Name width (skip hex/padding and containers)
|
// Name width (skip hex/padding)
|
||||||
if (!isHexPreview(child.kind) &&
|
if (!isHexPreview(child.kind)) {
|
||||||
child.kind != NodeKind::Struct && child.kind != NodeKind::Array) {
|
|
||||||
scopeMaxName = qMax(scopeMaxName, child.name.size());
|
scopeMaxName = qMax(scopeMaxName, child.name.size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state.scopeTypeW[container.id] = qBound(kMinTypeW, scopeMaxType + 1, kMaxTypeW);
|
state.scopeTypeW[container.id] = qBound(kMinTypeW, scopeMaxType, kMaxTypeW);
|
||||||
state.scopeNameW[container.id] = qBound(kMinNameW, scopeMaxName + 1, kMaxNameW);
|
state.scopeNameW[container.id] = qBound(kMinNameW, scopeMaxName, kMaxNameW);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute scope widths for root level (parentId == 0)
|
||||||
|
{
|
||||||
|
int rootMaxType = kMinTypeW;
|
||||||
|
int rootMaxName = kMinNameW;
|
||||||
|
for (int childIdx : state.childMap.value(0)) {
|
||||||
|
const Node& child = tree.nodes[childIdx];
|
||||||
|
|
||||||
|
// Skip containers - their headers don't use columnar layout
|
||||||
|
if (child.kind == NodeKind::Struct || child.kind == NodeKind::Array)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
QString childTypeName = fmt::typeNameRaw(child.kind);
|
||||||
|
rootMaxType = qMax(rootMaxType, childTypeName.size());
|
||||||
|
if (!isHexPreview(child.kind)) {
|
||||||
|
rootMaxName = qMax(rootMaxName, child.name.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state.scopeTypeW[0] = qBound(kMinTypeW, rootMaxType, kMaxTypeW);
|
||||||
|
state.scopeNameW[0] = qBound(kMinNameW, rootMaxName, kMaxNameW);
|
||||||
}
|
}
|
||||||
|
|
||||||
QVector<int> roots = state.childMap.value(0);
|
QVector<int> roots = state.childMap.value(0);
|
||||||
|
|||||||
@@ -151,13 +151,12 @@ void RcxController::connectEditor(RcxEditor* editor) {
|
|||||||
bool typeOk;
|
bool typeOk;
|
||||||
NodeKind elemKind = kindFromTypeName(elemTypeName, &typeOk);
|
NodeKind elemKind = kindFromTypeName(elemTypeName, &typeOk);
|
||||||
if (typeOk && nodeIdx < m_doc->tree.nodes.size()) {
|
if (typeOk && nodeIdx < m_doc->tree.nodes.size()) {
|
||||||
Node& node = m_doc->tree.nodes[nodeIdx];
|
const Node& node = m_doc->tree.nodes[nodeIdx];
|
||||||
if (node.kind == NodeKind::Array) {
|
if (node.kind == NodeKind::Array) {
|
||||||
// Update element kind and count (no undo for now)
|
m_doc->undoStack.push(new RcxCommand(this,
|
||||||
node.elementKind = elemKind;
|
cmd::ChangeArrayMeta{node.id,
|
||||||
node.arrayLen = newCount;
|
node.elementKind, elemKind,
|
||||||
if (node.viewIndex >= newCount)
|
node.arrayLen, newCount}));
|
||||||
node.viewIndex = qMax(0, newCount - 1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -446,6 +445,14 @@ void RcxController::applyCommand(const Command& command, bool isUndo) {
|
|||||||
} else if constexpr (std::is_same_v<T, cmd::WriteBytes>) {
|
} else if constexpr (std::is_same_v<T, cmd::WriteBytes>) {
|
||||||
const QByteArray& bytes = isUndo ? c.oldBytes : c.newBytes;
|
const QByteArray& bytes = isUndo ? c.oldBytes : c.newBytes;
|
||||||
m_doc->provider->writeBytes(c.addr, bytes);
|
m_doc->provider->writeBytes(c.addr, bytes);
|
||||||
|
} else if constexpr (std::is_same_v<T, cmd::ChangeArrayMeta>) {
|
||||||
|
int idx = tree.indexOfId(c.nodeId);
|
||||||
|
if (idx >= 0) {
|
||||||
|
tree.nodes[idx].elementKind = isUndo ? c.oldElementKind : c.newElementKind;
|
||||||
|
tree.nodes[idx].arrayLen = isUndo ? c.oldArrayLen : c.newArrayLen;
|
||||||
|
if (tree.nodes[idx].viewIndex >= tree.nodes[idx].arrayLen)
|
||||||
|
tree.nodes[idx].viewIndex = qMax(0, tree.nodes[idx].arrayLen - 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, command);
|
}, command);
|
||||||
|
|
||||||
|
|||||||
33
src/core.h
33
src/core.h
@@ -382,6 +382,12 @@ struct NodeTree {
|
|||||||
|
|
||||||
int structSpan(uint64_t structId,
|
int structSpan(uint64_t structId,
|
||||||
const QHash<uint64_t, QVector<int>>* childMap = nullptr) const {
|
const QHash<uint64_t, QVector<int>>* childMap = nullptr) const {
|
||||||
|
int idx = indexOfId(structId);
|
||||||
|
if (idx < 0) return 0;
|
||||||
|
|
||||||
|
const Node& node = nodes[idx];
|
||||||
|
int declaredSize = node.byteSize();
|
||||||
|
|
||||||
int maxEnd = 0;
|
int maxEnd = 0;
|
||||||
QVector<int> kids = childMap ? childMap->value(structId) : childrenOf(structId);
|
QVector<int> kids = childMap ? childMap->value(structId) : childrenOf(structId);
|
||||||
for (int ci : kids) {
|
for (int ci : kids) {
|
||||||
@@ -391,7 +397,8 @@ struct NodeTree {
|
|||||||
int end = c.offset + sz;
|
int end = c.offset + sz;
|
||||||
if (end > maxEnd) maxEnd = end;
|
if (end > maxEnd) maxEnd = end;
|
||||||
}
|
}
|
||||||
return maxEnd;
|
|
||||||
|
return qMax(declaredSize, maxEnd);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Batch selection normalizers
|
// Batch selection normalizers
|
||||||
@@ -480,11 +487,15 @@ namespace cmd {
|
|||||||
QVector<OffsetAdj> offAdjs; };
|
QVector<OffsetAdj> offAdjs; };
|
||||||
struct ChangeBase { uint64_t oldBase, newBase; };
|
struct ChangeBase { uint64_t oldBase, newBase; };
|
||||||
struct WriteBytes { uint64_t addr; QByteArray oldBytes, newBytes; };
|
struct WriteBytes { uint64_t addr; QByteArray oldBytes, newBytes; };
|
||||||
|
struct ChangeArrayMeta { uint64_t nodeId;
|
||||||
|
NodeKind oldElementKind, newElementKind;
|
||||||
|
int oldArrayLen, newArrayLen; };
|
||||||
}
|
}
|
||||||
|
|
||||||
using Command = std::variant<
|
using Command = std::variant<
|
||||||
cmd::ChangeKind, cmd::Rename, cmd::Collapse,
|
cmd::ChangeKind, cmd::Rename, cmd::Collapse,
|
||||||
cmd::Insert, cmd::Remove, cmd::ChangeBase, cmd::WriteBytes
|
cmd::Insert, cmd::Remove, cmd::ChangeBase, cmd::WriteBytes,
|
||||||
|
cmd::ChangeArrayMeta
|
||||||
>;
|
>;
|
||||||
|
|
||||||
// ── Column spans (for inline editing) ──
|
// ── Column spans (for inline editing) ──
|
||||||
@@ -504,7 +515,7 @@ inline constexpr int kColName = 22;
|
|||||||
inline constexpr int kColValue = 32;
|
inline constexpr int kColValue = 32;
|
||||||
inline constexpr int kColComment = 28; // "// Enter=Save Esc=Cancel" fits
|
inline constexpr int kColComment = 28; // "// Enter=Save Esc=Cancel" fits
|
||||||
inline constexpr int kColBaseAddr = 12; // "0x" + up to 10 hex digits (40-bit address)
|
inline constexpr int kColBaseAddr = 12; // "0x" + up to 10 hex digits (40-bit address)
|
||||||
inline constexpr int kSepWidth = 2;
|
inline constexpr int kSepWidth = 1;
|
||||||
inline constexpr int kMinTypeW = 8; // Minimum type column width (fits "uint64_t")
|
inline constexpr int kMinTypeW = 8; // Minimum type column width (fits "uint64_t")
|
||||||
inline constexpr int kMaxTypeW = 14; // Maximum type column width (fits "uint64_t[999]")
|
inline constexpr int kMaxTypeW = 14; // Maximum type column width (fits "uint64_t[999]")
|
||||||
inline constexpr int kMinNameW = 8; // Minimum name column width (matches ASCII preview)
|
inline constexpr int kMinNameW = 8; // Minimum name column width (matches ASCII preview)
|
||||||
@@ -541,7 +552,7 @@ inline ColumnSpan valueSpanFor(const LineMeta& lm, int /*lineLength*/, int typeW
|
|||||||
if (lm.isContinuation) {
|
if (lm.isContinuation) {
|
||||||
int prefixW = isHexPad
|
int prefixW = isHexPad
|
||||||
? (typeW + kSepWidth + 8 + kSepWidth)
|
? (typeW + kSepWidth + 8 + kSepWidth)
|
||||||
: (typeW + nameW + 4);
|
: (typeW + nameW + 2 * kSepWidth);
|
||||||
int start = ind + prefixW;
|
int start = ind + prefixW;
|
||||||
return {start, start + valWidth, true};
|
return {start, start + valWidth, true};
|
||||||
}
|
}
|
||||||
@@ -564,7 +575,7 @@ inline ColumnSpan commentSpanFor(const LineMeta& lm, int lineLength, int typeW =
|
|||||||
if (lm.isContinuation) {
|
if (lm.isContinuation) {
|
||||||
int prefixW = isHexPad
|
int prefixW = isHexPad
|
||||||
? (typeW + kSepWidth + 8 + kSepWidth)
|
? (typeW + kSepWidth + 8 + kSepWidth)
|
||||||
: (typeW + nameW + 4);
|
: (typeW + nameW + 2 * kSepWidth);
|
||||||
start = ind + prefixW + valWidth;
|
start = ind + prefixW + valWidth;
|
||||||
} else {
|
} else {
|
||||||
start = isHexPad
|
start = isHexPad
|
||||||
@@ -575,13 +586,13 @@ inline ColumnSpan commentSpanFor(const LineMeta& lm, int lineLength, int typeW =
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Base address span (only valid for root struct headers)
|
// Base address span (only valid for root struct headers)
|
||||||
// Line format: " - struct Name { base: 0x00400000"
|
// Line format: " - struct Name { // base: 0x00400000"
|
||||||
inline ColumnSpan baseAddressSpanFor(const LineMeta& lm, const QString& lineText) {
|
inline ColumnSpan baseAddressSpanFor(const LineMeta& lm, const QString& lineText) {
|
||||||
if (lm.lineKind != LineKind::Header || !lm.isRootHeader) return {};
|
if (lm.lineKind != LineKind::Header || !lm.isRootHeader) return {};
|
||||||
// Find "base: " after the opening brace
|
// Find "// base: " after the opening brace
|
||||||
int baseIdx = lineText.indexOf(QStringLiteral("base: "));
|
int baseIdx = lineText.indexOf(QStringLiteral("// base: "));
|
||||||
if (baseIdx < 0) return {};
|
if (baseIdx < 0) return {};
|
||||||
int startPos = baseIdx + 6; // after "base: "
|
int startPos = baseIdx + 9; // after "// base: "
|
||||||
// Value goes to end of line
|
// Value goes to end of line
|
||||||
int endPos = lineText.size();
|
int endPos = lineText.size();
|
||||||
while (endPos > startPos && lineText[endPos-1].isSpace())
|
while (endPos > startPos && lineText[endPos-1].isSpace())
|
||||||
@@ -590,10 +601,10 @@ inline ColumnSpan baseAddressSpanFor(const LineMeta& lm, const QString& lineText
|
|||||||
return {startPos, endPos, true};
|
return {startPos, endPos, true};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Full "base: 0x..." span for coloring (includes "base: " prefix)
|
// Full "// base: 0x..." span for coloring (includes "// base: " prefix)
|
||||||
inline ColumnSpan baseAddressFullSpanFor(const LineMeta& lm, const QString& lineText) {
|
inline ColumnSpan baseAddressFullSpanFor(const LineMeta& lm, const QString& lineText) {
|
||||||
if (lm.lineKind != LineKind::Header || !lm.isRootHeader) return {};
|
if (lm.lineKind != LineKind::Header || !lm.isRootHeader) return {};
|
||||||
int baseIdx = lineText.indexOf(QStringLiteral("base: "));
|
int baseIdx = lineText.indexOf(QStringLiteral("// base: "));
|
||||||
if (baseIdx < 0) return {};
|
if (baseIdx < 0) return {};
|
||||||
int endPos = lineText.size();
|
int endPos = lineText.size();
|
||||||
while (endPos > baseIdx && lineText[endPos-1].isSpace())
|
while (endPos > baseIdx && lineText[endPos-1].isSpace())
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ void RcxEditor::setupScintilla() {
|
|||||||
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETSTYLE,
|
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETSTYLE,
|
||||||
IND_BASE_ADDR, 17 /*INDIC_TEXTFORE*/);
|
IND_BASE_ADDR, 17 /*INDIC_TEXTFORE*/);
|
||||||
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETFORE,
|
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETFORE,
|
||||||
IND_BASE_ADDR, QColor("#6a9955"));
|
IND_BASE_ADDR, QColor("#5a8248"));
|
||||||
|
|
||||||
// Hover span indicator — blue text like a link
|
// Hover span indicator — blue text like a link
|
||||||
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETSTYLE,
|
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETSTYLE,
|
||||||
@@ -517,10 +517,8 @@ void RcxEditor::applyBaseAddressColoring(const QVector<LineMeta>& meta) {
|
|||||||
QString lineText = getLineText(m_sci, i);
|
QString lineText = getLineText(m_sci, i);
|
||||||
ColumnSpan span = baseAddressFullSpanFor(lm, lineText);
|
ColumnSpan span = baseAddressFullSpanFor(lm, lineText);
|
||||||
if (!span.valid) continue;
|
if (!span.valid) continue;
|
||||||
long lineStart = m_sci->SendScintilla(QsciScintillaBase::SCI_POSITIONFROMLINE,
|
long posA = posFromCol(m_sci, i, span.start);
|
||||||
(unsigned long)i);
|
long posB = posFromCol(m_sci, i, span.end);
|
||||||
long posA = lineStart + span.start;
|
|
||||||
long posB = lineStart + span.end;
|
|
||||||
if (posB > posA)
|
if (posB > posA)
|
||||||
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICATORFILLRANGE, posA, posB - posA);
|
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICATORFILLRANGE, posA, posB - posA);
|
||||||
}
|
}
|
||||||
@@ -1381,11 +1379,9 @@ void RcxEditor::setEditComment(const QString& comment) {
|
|||||||
QString formatted = QStringLiteral("//") + comment;
|
QString formatted = QStringLiteral("//") + comment;
|
||||||
QString padded = formatted.leftJustified(availWidth, ' ').left(availWidth);
|
QString padded = formatted.leftJustified(availWidth, ' ').left(availWidth);
|
||||||
|
|
||||||
// Use direct position calculation from line start
|
// Use UTF-8 safe column-to-position conversion
|
||||||
long lineStart = m_sci->SendScintilla(QsciScintillaBase::SCI_POSITIONFROMLINE,
|
long posA = posFromCol(m_sci, m_editState.line, startCol);
|
||||||
(unsigned long)m_editState.line);
|
long posB = posFromCol(m_sci, m_editState.line, endCol);
|
||||||
long posA = lineStart + startCol;
|
|
||||||
long posB = lineStart + endCol;
|
|
||||||
|
|
||||||
QByteArray utf8 = padded.toUtf8();
|
QByteArray utf8 = padded.toUtf8();
|
||||||
m_sci->SendScintilla(QsciScintillaBase::SCI_SETTARGETSTART, posA);
|
m_sci->SendScintilla(QsciScintillaBase::SCI_SETTARGETSTART, posA);
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ static constexpr int COL_TYPE = kColType;
|
|||||||
static constexpr int COL_NAME = kColName;
|
static constexpr int COL_NAME = kColName;
|
||||||
static constexpr int COL_VALUE = kColValue;
|
static constexpr int COL_VALUE = kColValue;
|
||||||
static constexpr int COL_COMMENT = 28; // "// Enter=Save Esc=Cancel" fits
|
static constexpr int COL_COMMENT = 28; // "// Enter=Save Esc=Cancel" fits
|
||||||
static const QString SEP = QStringLiteral(" ");
|
static const QString SEP = QStringLiteral(" ");
|
||||||
|
|
||||||
static QString fit(QString s, int w) {
|
static QString fit(QString s, int w) {
|
||||||
if (w <= 0) return {};
|
if (w <= 0) return {};
|
||||||
@@ -94,6 +94,8 @@ QString indent(int depth) {
|
|||||||
|
|
||||||
QString fmtOffsetMargin(int64_t relativeOffset, bool isContinuation) {
|
QString fmtOffsetMargin(int64_t relativeOffset, bool isContinuation) {
|
||||||
if (isContinuation) return QStringLiteral(" \u00B7");
|
if (isContinuation) return QStringLiteral(" \u00B7");
|
||||||
|
if (relativeOffset < 0)
|
||||||
|
return QStringLiteral("-0x") + QString::number(-relativeOffset, 16).toUpper();
|
||||||
return QStringLiteral("+0x") + QString::number(relativeOffset, 16).toUpper();
|
return QStringLiteral("+0x") + QString::number(relativeOffset, 16).toUpper();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,7 +111,7 @@ QString fmtStructHeaderWithBase(const Node& node, int depth, uint64_t baseAddres
|
|||||||
QString header = indent(depth) + typeName(node.kind).trimmed() +
|
QString header = indent(depth) + typeName(node.kind).trimmed() +
|
||||||
QStringLiteral(" ") + node.name + QStringLiteral(" { ");
|
QStringLiteral(" ") + node.name + QStringLiteral(" { ");
|
||||||
QString baseHex = QStringLiteral("0x") + QString::number(baseAddress, 16).toUpper();
|
QString baseHex = QStringLiteral("0x") + QString::number(baseAddress, 16).toUpper();
|
||||||
return header + QStringLiteral("base: ") + baseHex;
|
return header + QStringLiteral("// base: ") + baseHex;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString fmtStructFooter(const Node& /*node*/, int depth, int /*totalSize*/) {
|
QString fmtStructFooter(const Node& /*node*/, int depth, int /*totalSize*/) {
|
||||||
@@ -255,7 +257,7 @@ QString fmtNodeLine(const Node& node, const Provider& prov,
|
|||||||
QString type = typeName(node.kind, colType);
|
QString type = typeName(node.kind, colType);
|
||||||
QString name = fit(node.name, colName);
|
QString name = fit(node.name, colName);
|
||||||
// Blank prefix for continuation lines (same width as type+sep+name+sep)
|
// Blank prefix for continuation lines (same width as type+sep+name+sep)
|
||||||
const int prefixW = colType + colName + 4; // 2 seps × 2 chars
|
const int prefixW = colType + colName + 2 * kSepWidth;
|
||||||
|
|
||||||
// Comment suffix (padded or empty)
|
// Comment suffix (padded or empty)
|
||||||
QString cmtSuffix = comment.isEmpty() ? QString(COL_COMMENT, ' ')
|
QString cmtSuffix = comment.isEmpty() ? QString(COL_COMMENT, ' ')
|
||||||
@@ -384,13 +386,62 @@ QByteArray parseValue(NodeKind kind, const QString& text, bool* ok) {
|
|||||||
|
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
case NodeKind::Hex8: return parseHexBytes(stripHex(s), 1, ok);
|
case NodeKind::Hex8: return parseHexBytes(stripHex(s), 1, ok);
|
||||||
case NodeKind::Hex16: return parseHexBytes(stripHex(s), 2, ok);
|
case NodeKind::Hex16: {
|
||||||
case NodeKind::Hex32: return parseHexBytes(stripHex(s), 4, ok);
|
uint val = stripHex(s).toUInt(ok, 16);
|
||||||
case NodeKind::Hex64: return parseHexBytes(stripHex(s), 8, ok);
|
if (*ok && val > 0xFFFF) *ok = false;
|
||||||
case NodeKind::Int8: { int b = s.startsWith("0x",Qt::CaseInsensitive)?16:10; int val = stripHex(s).toInt(ok,b); return parseIntChecked<int8_t>(val, ok); }
|
return *ok ? toBytes<uint16_t>(static_cast<uint16_t>(val)) : QByteArray{};
|
||||||
case NodeKind::Int16: { int b = s.startsWith("0x",Qt::CaseInsensitive)?16:10; int val = stripHex(s).toInt(ok,b); return parseIntChecked<int16_t>(val, ok); }
|
}
|
||||||
case NodeKind::Int32: { int b = s.startsWith("0x",Qt::CaseInsensitive)?16:10; int val = stripHex(s).toInt(ok,b); return *ok ? toBytes<int32_t>(val) : QByteArray{}; }
|
case NodeKind::Hex32: {
|
||||||
case NodeKind::Int64: { int b = s.startsWith("0x",Qt::CaseInsensitive)?16:10; qlonglong val = stripHex(s).toLongLong(ok,b); return *ok ? toBytes<int64_t>(val) : QByteArray{}; }
|
uint val = stripHex(s).toUInt(ok, 16);
|
||||||
|
return *ok ? toBytes<uint32_t>(val) : QByteArray{};
|
||||||
|
}
|
||||||
|
case NodeKind::Hex64: {
|
||||||
|
qulonglong val = stripHex(s).toULongLong(ok, 16);
|
||||||
|
return *ok ? toBytes<uint64_t>(val) : QByteArray{};
|
||||||
|
}
|
||||||
|
case NodeKind::Int8: {
|
||||||
|
bool isHex = s.startsWith("0x", Qt::CaseInsensitive);
|
||||||
|
if (isHex) {
|
||||||
|
uint val = stripHex(s).toUInt(ok, 16);
|
||||||
|
if (*ok && val > 0xFF) *ok = false;
|
||||||
|
return *ok ? toBytes<int8_t>(static_cast<int8_t>(val)) : QByteArray{};
|
||||||
|
} else {
|
||||||
|
int val = s.toInt(ok, 10);
|
||||||
|
return parseIntChecked<int8_t>(val, ok);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case NodeKind::Int16: {
|
||||||
|
bool isHex = s.startsWith("0x", Qt::CaseInsensitive);
|
||||||
|
if (isHex) {
|
||||||
|
uint val = stripHex(s).toUInt(ok, 16);
|
||||||
|
if (*ok && val > 0xFFFF) *ok = false;
|
||||||
|
return *ok ? toBytes<int16_t>(static_cast<int16_t>(val)) : QByteArray{};
|
||||||
|
} else {
|
||||||
|
int val = s.toInt(ok, 10);
|
||||||
|
return parseIntChecked<int16_t>(val, ok);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case NodeKind::Int32: {
|
||||||
|
bool isHex = s.startsWith("0x", Qt::CaseInsensitive);
|
||||||
|
if (isHex) {
|
||||||
|
qulonglong val = stripHex(s).toULongLong(ok, 16);
|
||||||
|
if (*ok && val > 0xFFFFFFFFULL) *ok = false;
|
||||||
|
return *ok ? toBytes<int32_t>(static_cast<int32_t>(val)) : QByteArray{};
|
||||||
|
} else {
|
||||||
|
int val = s.toInt(ok, 10);
|
||||||
|
return *ok ? toBytes<int32_t>(val) : QByteArray{};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case NodeKind::Int64: {
|
||||||
|
bool isHex = s.startsWith("0x", Qt::CaseInsensitive);
|
||||||
|
if (isHex) {
|
||||||
|
qulonglong val = stripHex(s).toULongLong(ok, 16);
|
||||||
|
return *ok ? toBytes<int64_t>(static_cast<int64_t>(val)) : QByteArray{};
|
||||||
|
} else {
|
||||||
|
qlonglong val = s.toLongLong(ok, 10);
|
||||||
|
return *ok ? toBytes<int64_t>(val) : QByteArray{};
|
||||||
|
}
|
||||||
|
}
|
||||||
case NodeKind::UInt8: { int b = s.startsWith("0x",Qt::CaseInsensitive)?16:10; uint val = stripHex(s).toUInt(ok,b); return parseIntChecked<uint8_t>(val, ok); }
|
case NodeKind::UInt8: { int b = s.startsWith("0x",Qt::CaseInsensitive)?16:10; uint val = stripHex(s).toUInt(ok,b); return parseIntChecked<uint8_t>(val, ok); }
|
||||||
case NodeKind::UInt16: { int b = s.startsWith("0x",Qt::CaseInsensitive)?16:10; uint val = stripHex(s).toUInt(ok,b); return parseIntChecked<uint16_t>(val, ok); }
|
case NodeKind::UInt16: { int b = s.startsWith("0x",Qt::CaseInsensitive)?16:10; uint val = stripHex(s).toUInt(ok,b); return parseIntChecked<uint16_t>(val, ok); }
|
||||||
case NodeKind::UInt32: { int b = s.startsWith("0x",Qt::CaseInsensitive)?16:10; uint val = stripHex(s).toUInt(ok,b); return *ok ? toBytes<uint32_t>(val) : QByteArray{}; }
|
case NodeKind::UInt32: { int b = s.startsWith("0x",Qt::CaseInsensitive)?16:10; uint val = stripHex(s).toUInt(ok,b); return *ok ? toBytes<uint32_t>(val) : QByteArray{}; }
|
||||||
|
|||||||
@@ -330,13 +330,13 @@ private slots:
|
|||||||
|
|
||||||
auto ns = rcx::nameSpanFor(lm);
|
auto ns = rcx::nameSpanFor(lm);
|
||||||
QVERIFY(ns.valid);
|
QVERIFY(ns.valid);
|
||||||
QCOMPARE(ns.start, 22); // 6 + 14 + 2
|
QCOMPARE(ns.start, 21); // 6 + 14 + 1 (kSepWidth)
|
||||||
QCOMPARE(ns.end, 44); // 22 + 22 (kColName)
|
QCOMPARE(ns.end, 43); // 21 + 22 (kColName)
|
||||||
|
|
||||||
auto vs = rcx::valueSpanFor(lm, 100);
|
auto vs = rcx::valueSpanFor(lm, 100);
|
||||||
QVERIFY(vs.valid);
|
QVERIFY(vs.valid);
|
||||||
QCOMPARE(vs.start, 46); // 22 + 22 + 2
|
QCOMPARE(vs.start, 44); // 21 + 22 + 1 (kSepWidth)
|
||||||
QCOMPARE(vs.end, 78); // 46 + 32 (kColValue)
|
QCOMPARE(vs.end, 76); // 44 + 32 (kColValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
void testColumnSpan_continuation() {
|
void testColumnSpan_continuation() {
|
||||||
@@ -351,8 +351,8 @@ private slots:
|
|||||||
|
|
||||||
auto vs = rcx::valueSpanFor(lm, 100);
|
auto vs = rcx::valueSpanFor(lm, 100);
|
||||||
QVERIFY(vs.valid);
|
QVERIFY(vs.valid);
|
||||||
QCOMPARE(vs.start, 6 + 14 + 22 + 4); // kFoldCol+indent + kColType(14) + kColName(22) + 4
|
QCOMPARE(vs.start, 6 + 14 + 22 + 2); // kFoldCol+indent + kColType(14) + kColName(22) + 2*kSepWidth
|
||||||
QCOMPARE(vs.end, 46 + 32); // start + kColValue
|
QCOMPARE(vs.end, 44 + 32); // start + kColValue
|
||||||
}
|
}
|
||||||
|
|
||||||
void testColumnSpan_headerFooter() {
|
void testColumnSpan_headerFooter() {
|
||||||
@@ -386,13 +386,13 @@ private slots:
|
|||||||
|
|
||||||
auto ns = rcx::nameSpanFor(lm);
|
auto ns = rcx::nameSpanFor(lm);
|
||||||
QVERIFY(ns.valid);
|
QVERIFY(ns.valid);
|
||||||
QCOMPARE(ns.start, 19); // 3 + 14 + 2
|
QCOMPARE(ns.start, 18); // 3 + 14 + 1 (kSepWidth)
|
||||||
QCOMPARE(ns.end, 41); // 19 + 22 (kColName)
|
QCOMPARE(ns.end, 40); // 18 + 22 (kColName)
|
||||||
|
|
||||||
auto vs = rcx::valueSpanFor(lm, 100);
|
auto vs = rcx::valueSpanFor(lm, 100);
|
||||||
QVERIFY(vs.valid);
|
QVERIFY(vs.valid);
|
||||||
QCOMPARE(vs.start, 43); // 19 + 22 + 2
|
QCOMPARE(vs.start, 41); // 18 + 22 + 1 (kSepWidth)
|
||||||
QCOMPARE(vs.end, 75); // 43 + 32 (kColValue)
|
QCOMPARE(vs.end, 73); // 41 + 32 (kColValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
void testNodeIdJsonRoundTrip() {
|
void testNodeIdJsonRoundTrip() {
|
||||||
@@ -474,6 +474,38 @@ private slots:
|
|||||||
empty.parentId = 0;
|
empty.parentId = 0;
|
||||||
int ei = tree3.addNode(empty);
|
int ei = tree3.addNode(empty);
|
||||||
QCOMPARE(tree3.structSpan(tree3.nodes[ei].id), 0);
|
QCOMPARE(tree3.structSpan(tree3.nodes[ei].id), 0);
|
||||||
|
|
||||||
|
// Primitive array (no children) should return its declared size
|
||||||
|
NodeTree tree4;
|
||||||
|
Node arr;
|
||||||
|
arr.kind = NodeKind::Array;
|
||||||
|
arr.name = "data";
|
||||||
|
arr.parentId = 0;
|
||||||
|
arr.arrayLen = 16;
|
||||||
|
arr.elementKind = NodeKind::UInt32; // 16 * 4 = 64 bytes
|
||||||
|
int ai = tree4.addNode(arr);
|
||||||
|
QCOMPARE(tree4.structSpan(tree4.nodes[ai].id), 64);
|
||||||
|
|
||||||
|
// Struct containing primitive array - span includes array size
|
||||||
|
NodeTree tree5;
|
||||||
|
Node container;
|
||||||
|
container.kind = NodeKind::Struct;
|
||||||
|
container.name = "Container";
|
||||||
|
container.parentId = 0;
|
||||||
|
int ci = tree5.addNode(container);
|
||||||
|
uint64_t containerId = tree5.nodes[ci].id;
|
||||||
|
|
||||||
|
Node arr2;
|
||||||
|
arr2.kind = NodeKind::Array;
|
||||||
|
arr2.name = "items";
|
||||||
|
arr2.parentId = containerId;
|
||||||
|
arr2.offset = 8;
|
||||||
|
arr2.arrayLen = 10;
|
||||||
|
arr2.elementKind = NodeKind::UInt64; // 10 * 8 = 80 bytes
|
||||||
|
tree5.addNode(arr2);
|
||||||
|
|
||||||
|
// Container span = array offset (8) + array size (80) = 88
|
||||||
|
QCOMPARE(tree5.structSpan(containerId), 88);
|
||||||
}
|
}
|
||||||
void testNormalizePreferAncestors() {
|
void testNormalizePreferAncestors() {
|
||||||
using namespace rcx;
|
using namespace rcx;
|
||||||
|
|||||||
@@ -94,14 +94,14 @@ private slots:
|
|||||||
|
|
||||||
void testParseValueHex32() {
|
void testParseValueHex32() {
|
||||||
bool ok;
|
bool ok;
|
||||||
// Hex parsing produces raw bytes (no endian conversion)
|
// Hex parsing produces native-endian bytes (matches display which reads native-endian)
|
||||||
QByteArray b = fmt::parseValue(NodeKind::Hex32, "DEADBEEF", &ok);
|
QByteArray b = fmt::parseValue(NodeKind::Hex32, "DEADBEEF", &ok);
|
||||||
QVERIFY(ok);
|
QVERIFY(ok);
|
||||||
QCOMPARE(b.size(), 4);
|
QCOMPARE(b.size(), 4);
|
||||||
QCOMPARE((uint8_t)b[0], (uint8_t)0xDE);
|
// Value 0xDEADBEEF stored as native-endian (little-endian on x86)
|
||||||
QCOMPARE((uint8_t)b[1], (uint8_t)0xAD);
|
uint32_t v;
|
||||||
QCOMPARE((uint8_t)b[2], (uint8_t)0xBE);
|
memcpy(&v, b.data(), 4);
|
||||||
QCOMPARE((uint8_t)b[3], (uint8_t)0xEF);
|
QCOMPARE(v, (uint32_t)0xDEADBEEF);
|
||||||
}
|
}
|
||||||
|
|
||||||
void testParseValueBool() {
|
void testParseValueBool() {
|
||||||
@@ -122,13 +122,12 @@ private slots:
|
|||||||
|
|
||||||
void testParseValueHex0xPrefix() {
|
void testParseValueHex0xPrefix() {
|
||||||
bool ok;
|
bool ok;
|
||||||
// Hex32 with 0x prefix should work (raw bytes, no endian conversion)
|
// Hex32 with 0x prefix should work (native-endian, matches display)
|
||||||
QByteArray b = fmt::parseValue(NodeKind::Hex32, "0xDEADBEEF", &ok);
|
QByteArray b = fmt::parseValue(NodeKind::Hex32, "0xDEADBEEF", &ok);
|
||||||
QVERIFY(ok);
|
QVERIFY(ok);
|
||||||
QCOMPARE((uint8_t)b[0], (uint8_t)0xDE);
|
uint32_t v32;
|
||||||
QCOMPARE((uint8_t)b[1], (uint8_t)0xAD);
|
memcpy(&v32, b.data(), 4);
|
||||||
QCOMPARE((uint8_t)b[2], (uint8_t)0xBE);
|
QCOMPARE(v32, (uint32_t)0xDEADBEEF);
|
||||||
QCOMPARE((uint8_t)b[3], (uint8_t)0xEF);
|
|
||||||
|
|
||||||
// Pointer64 with 0x prefix
|
// Pointer64 with 0x prefix
|
||||||
b = fmt::parseValue(NodeKind::Pointer64, "0x0000000000400000", &ok);
|
b = fmt::parseValue(NodeKind::Pointer64, "0x0000000000400000", &ok);
|
||||||
@@ -177,6 +176,44 @@ private slots:
|
|||||||
QVERIFY(!ok);
|
QVERIFY(!ok);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void testSignedHexRoundTrip() {
|
||||||
|
bool ok;
|
||||||
|
// Int8: 0xFF should parse as -1 (two's complement)
|
||||||
|
QByteArray b = fmt::parseValue(NodeKind::Int8, "0xFF", &ok);
|
||||||
|
QVERIFY(ok);
|
||||||
|
int8_t sv8;
|
||||||
|
memcpy(&sv8, b.data(), 1);
|
||||||
|
QCOMPARE(sv8, (int8_t)-1);
|
||||||
|
|
||||||
|
// Int8: 0x80 should parse as -128
|
||||||
|
b = fmt::parseValue(NodeKind::Int8, "0x80", &ok);
|
||||||
|
QVERIFY(ok);
|
||||||
|
memcpy(&sv8, b.data(), 1);
|
||||||
|
QCOMPARE(sv8, (int8_t)-128);
|
||||||
|
|
||||||
|
// Int16: 0xFFFF should parse as -1
|
||||||
|
b = fmt::parseValue(NodeKind::Int16, "0xFFFF", &ok);
|
||||||
|
QVERIFY(ok);
|
||||||
|
int16_t sv16;
|
||||||
|
memcpy(&sv16, b.data(), 2);
|
||||||
|
QCOMPARE(sv16, (int16_t)-1);
|
||||||
|
|
||||||
|
// Int32: 0xFFFFFFFF should parse as -1
|
||||||
|
b = fmt::parseValue(NodeKind::Int32, "0xFFFFFFFF", &ok);
|
||||||
|
QVERIFY(ok);
|
||||||
|
int32_t sv32;
|
||||||
|
memcpy(&sv32, b.data(), 4);
|
||||||
|
QCOMPARE(sv32, (int32_t)-1);
|
||||||
|
|
||||||
|
// Int8: 0x1FF should fail (exceeds byte range)
|
||||||
|
fmt::parseValue(NodeKind::Int8, "0x1FF", &ok);
|
||||||
|
QVERIFY(!ok);
|
||||||
|
|
||||||
|
// Int16: 0x1FFFF should fail (exceeds 16-bit range)
|
||||||
|
fmt::parseValue(NodeKind::Int16, "0x1FFFF", &ok);
|
||||||
|
QVERIFY(!ok);
|
||||||
|
}
|
||||||
|
|
||||||
void testReadValueBoundsCheck() {
|
void testReadValueBoundsCheck() {
|
||||||
// Vec2 subLine=2 (out of bounds) should return "?"
|
// Vec2 subLine=2 (out of bounds) should return "?"
|
||||||
QByteArray data(16, '\0');
|
QByteArray data(16, '\0');
|
||||||
|
|||||||
Reference in New Issue
Block a user