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 typeW = kColType; // global type 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
|
||||
QHash<uint64_t, QVector<int>> childMap;
|
||||
@@ -206,7 +207,8 @@ void composeParent(ComposeState& state, const NodeTree& tree,
|
||||
lm.foldCollapsed = node.collapsed;
|
||||
lm.foldLevel = computeFoldLevel(depth, true);
|
||||
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;
|
||||
if (node.kind == NodeKind::Array) {
|
||||
@@ -350,7 +352,7 @@ ComposeResult compose(const NodeTree& tree, const Provider& prov) {
|
||||
}
|
||||
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
|
||||
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;
|
||||
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)
|
||||
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)) {
|
||||
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
|
||||
QString childTypeName;
|
||||
if (child.kind == NodeKind::Array) {
|
||||
childTypeName = fmt::arrayTypeName(child.elementKind, child.arrayLen);
|
||||
} else {
|
||||
childTypeName = fmt::typeNameRaw(child.kind);
|
||||
}
|
||||
QString childTypeName = fmt::typeNameRaw(child.kind);
|
||||
scopeMaxType = qMax(scopeMaxType, childTypeName.size());
|
||||
|
||||
// Name width (skip hex/padding and containers)
|
||||
if (!isHexPreview(child.kind) &&
|
||||
child.kind != NodeKind::Struct && child.kind != NodeKind::Array) {
|
||||
// Name width (skip hex/padding)
|
||||
if (!isHexPreview(child.kind)) {
|
||||
scopeMaxName = qMax(scopeMaxName, child.name.size());
|
||||
}
|
||||
}
|
||||
|
||||
state.scopeTypeW[container.id] = qBound(kMinTypeW, scopeMaxType + 1, kMaxTypeW);
|
||||
state.scopeNameW[container.id] = qBound(kMinNameW, scopeMaxName + 1, kMaxNameW);
|
||||
state.scopeTypeW[container.id] = qBound(kMinTypeW, scopeMaxType, kMaxTypeW);
|
||||
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);
|
||||
|
||||
@@ -151,13 +151,12 @@ void RcxController::connectEditor(RcxEditor* editor) {
|
||||
bool typeOk;
|
||||
NodeKind elemKind = kindFromTypeName(elemTypeName, &typeOk);
|
||||
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) {
|
||||
// Update element kind and count (no undo for now)
|
||||
node.elementKind = elemKind;
|
||||
node.arrayLen = newCount;
|
||||
if (node.viewIndex >= newCount)
|
||||
node.viewIndex = qMax(0, newCount - 1);
|
||||
m_doc->undoStack.push(new RcxCommand(this,
|
||||
cmd::ChangeArrayMeta{node.id,
|
||||
node.elementKind, elemKind,
|
||||
node.arrayLen, newCount}));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -446,6 +445,14 @@ void RcxController::applyCommand(const Command& command, bool isUndo) {
|
||||
} else if constexpr (std::is_same_v<T, cmd::WriteBytes>) {
|
||||
const QByteArray& bytes = isUndo ? c.oldBytes : c.newBytes;
|
||||
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);
|
||||
|
||||
|
||||
33
src/core.h
33
src/core.h
@@ -382,6 +382,12 @@ struct NodeTree {
|
||||
|
||||
int structSpan(uint64_t structId,
|
||||
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;
|
||||
QVector<int> kids = childMap ? childMap->value(structId) : childrenOf(structId);
|
||||
for (int ci : kids) {
|
||||
@@ -391,7 +397,8 @@ struct NodeTree {
|
||||
int end = c.offset + sz;
|
||||
if (end > maxEnd) maxEnd = end;
|
||||
}
|
||||
return maxEnd;
|
||||
|
||||
return qMax(declaredSize, maxEnd);
|
||||
}
|
||||
|
||||
// Batch selection normalizers
|
||||
@@ -480,11 +487,15 @@ namespace cmd {
|
||||
QVector<OffsetAdj> offAdjs; };
|
||||
struct ChangeBase { uint64_t oldBase, newBase; };
|
||||
struct WriteBytes { uint64_t addr; QByteArray oldBytes, newBytes; };
|
||||
struct ChangeArrayMeta { uint64_t nodeId;
|
||||
NodeKind oldElementKind, newElementKind;
|
||||
int oldArrayLen, newArrayLen; };
|
||||
}
|
||||
|
||||
using Command = std::variant<
|
||||
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) ──
|
||||
@@ -504,7 +515,7 @@ inline constexpr int kColName = 22;
|
||||
inline constexpr int kColValue = 32;
|
||||
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 kSepWidth = 2;
|
||||
inline constexpr int kSepWidth = 1;
|
||||
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 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) {
|
||||
int prefixW = isHexPad
|
||||
? (typeW + kSepWidth + 8 + kSepWidth)
|
||||
: (typeW + nameW + 4);
|
||||
: (typeW + nameW + 2 * kSepWidth);
|
||||
int start = ind + prefixW;
|
||||
return {start, start + valWidth, true};
|
||||
}
|
||||
@@ -564,7 +575,7 @@ inline ColumnSpan commentSpanFor(const LineMeta& lm, int lineLength, int typeW =
|
||||
if (lm.isContinuation) {
|
||||
int prefixW = isHexPad
|
||||
? (typeW + kSepWidth + 8 + kSepWidth)
|
||||
: (typeW + nameW + 4);
|
||||
: (typeW + nameW + 2 * kSepWidth);
|
||||
start = ind + prefixW + valWidth;
|
||||
} else {
|
||||
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)
|
||||
// Line format: " - struct Name { base: 0x00400000"
|
||||
// Line format: " - struct Name { // base: 0x00400000"
|
||||
inline ColumnSpan baseAddressSpanFor(const LineMeta& lm, const QString& lineText) {
|
||||
if (lm.lineKind != LineKind::Header || !lm.isRootHeader) return {};
|
||||
// Find "base: " after the opening brace
|
||||
int baseIdx = lineText.indexOf(QStringLiteral("base: "));
|
||||
// Find "// base: " after the opening brace
|
||||
int baseIdx = lineText.indexOf(QStringLiteral("// base: "));
|
||||
if (baseIdx < 0) return {};
|
||||
int startPos = baseIdx + 6; // after "base: "
|
||||
int startPos = baseIdx + 9; // after "// base: "
|
||||
// Value goes to end of line
|
||||
int endPos = lineText.size();
|
||||
while (endPos > startPos && lineText[endPos-1].isSpace())
|
||||
@@ -590,10 +601,10 @@ inline ColumnSpan baseAddressSpanFor(const LineMeta& lm, const QString& lineText
|
||||
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) {
|
||||
if (lm.lineKind != LineKind::Header || !lm.isRootHeader) return {};
|
||||
int baseIdx = lineText.indexOf(QStringLiteral("base: "));
|
||||
int baseIdx = lineText.indexOf(QStringLiteral("// base: "));
|
||||
if (baseIdx < 0) return {};
|
||||
int endPos = lineText.size();
|
||||
while (endPos > baseIdx && lineText[endPos-1].isSpace())
|
||||
|
||||
@@ -140,7 +140,7 @@ void RcxEditor::setupScintilla() {
|
||||
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETSTYLE,
|
||||
IND_BASE_ADDR, 17 /*INDIC_TEXTFORE*/);
|
||||
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETFORE,
|
||||
IND_BASE_ADDR, QColor("#6a9955"));
|
||||
IND_BASE_ADDR, QColor("#5a8248"));
|
||||
|
||||
// Hover span indicator — blue text like a link
|
||||
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETSTYLE,
|
||||
@@ -517,10 +517,8 @@ void RcxEditor::applyBaseAddressColoring(const QVector<LineMeta>& meta) {
|
||||
QString lineText = getLineText(m_sci, i);
|
||||
ColumnSpan span = baseAddressFullSpanFor(lm, lineText);
|
||||
if (!span.valid) continue;
|
||||
long lineStart = m_sci->SendScintilla(QsciScintillaBase::SCI_POSITIONFROMLINE,
|
||||
(unsigned long)i);
|
||||
long posA = lineStart + span.start;
|
||||
long posB = lineStart + span.end;
|
||||
long posA = posFromCol(m_sci, i, span.start);
|
||||
long posB = posFromCol(m_sci, i, span.end);
|
||||
if (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 padded = formatted.leftJustified(availWidth, ' ').left(availWidth);
|
||||
|
||||
// Use direct position calculation from line start
|
||||
long lineStart = m_sci->SendScintilla(QsciScintillaBase::SCI_POSITIONFROMLINE,
|
||||
(unsigned long)m_editState.line);
|
||||
long posA = lineStart + startCol;
|
||||
long posB = lineStart + endCol;
|
||||
// Use UTF-8 safe column-to-position conversion
|
||||
long posA = posFromCol(m_sci, m_editState.line, startCol);
|
||||
long posB = posFromCol(m_sci, m_editState.line, endCol);
|
||||
|
||||
QByteArray utf8 = padded.toUtf8();
|
||||
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_VALUE = kColValue;
|
||||
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) {
|
||||
if (w <= 0) return {};
|
||||
@@ -94,6 +94,8 @@ QString indent(int depth) {
|
||||
|
||||
QString fmtOffsetMargin(int64_t relativeOffset, bool isContinuation) {
|
||||
if (isContinuation) return QStringLiteral(" \u00B7");
|
||||
if (relativeOffset < 0)
|
||||
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() +
|
||||
QStringLiteral(" ") + node.name + QStringLiteral(" { ");
|
||||
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*/) {
|
||||
@@ -255,7 +257,7 @@ QString fmtNodeLine(const Node& node, const Provider& prov,
|
||||
QString type = typeName(node.kind, colType);
|
||||
QString name = fit(node.name, colName);
|
||||
// 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)
|
||||
QString cmtSuffix = comment.isEmpty() ? QString(COL_COMMENT, ' ')
|
||||
@@ -384,13 +386,62 @@ QByteArray parseValue(NodeKind kind, const QString& text, bool* ok) {
|
||||
|
||||
switch (kind) {
|
||||
case NodeKind::Hex8: return parseHexBytes(stripHex(s), 1, ok);
|
||||
case NodeKind::Hex16: return parseHexBytes(stripHex(s), 2, ok);
|
||||
case NodeKind::Hex32: return parseHexBytes(stripHex(s), 4, ok);
|
||||
case NodeKind::Hex64: return parseHexBytes(stripHex(s), 8, ok);
|
||||
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); }
|
||||
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::Int64: { int b = s.startsWith("0x",Qt::CaseInsensitive)?16:10; qlonglong val = stripHex(s).toLongLong(ok,b); return *ok ? toBytes<int64_t>(val) : QByteArray{}; }
|
||||
case NodeKind::Hex16: {
|
||||
uint val = stripHex(s).toUInt(ok, 16);
|
||||
if (*ok && val > 0xFFFF) *ok = false;
|
||||
return *ok ? toBytes<uint16_t>(static_cast<uint16_t>(val)) : QByteArray{};
|
||||
}
|
||||
case NodeKind::Hex32: {
|
||||
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::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{}; }
|
||||
|
||||
@@ -330,13 +330,13 @@ private slots:
|
||||
|
||||
auto ns = rcx::nameSpanFor(lm);
|
||||
QVERIFY(ns.valid);
|
||||
QCOMPARE(ns.start, 22); // 6 + 14 + 2
|
||||
QCOMPARE(ns.end, 44); // 22 + 22 (kColName)
|
||||
QCOMPARE(ns.start, 21); // 6 + 14 + 1 (kSepWidth)
|
||||
QCOMPARE(ns.end, 43); // 21 + 22 (kColName)
|
||||
|
||||
auto vs = rcx::valueSpanFor(lm, 100);
|
||||
QVERIFY(vs.valid);
|
||||
QCOMPARE(vs.start, 46); // 22 + 22 + 2
|
||||
QCOMPARE(vs.end, 78); // 46 + 32 (kColValue)
|
||||
QCOMPARE(vs.start, 44); // 21 + 22 + 1 (kSepWidth)
|
||||
QCOMPARE(vs.end, 76); // 44 + 32 (kColValue)
|
||||
}
|
||||
|
||||
void testColumnSpan_continuation() {
|
||||
@@ -351,8 +351,8 @@ private slots:
|
||||
|
||||
auto vs = rcx::valueSpanFor(lm, 100);
|
||||
QVERIFY(vs.valid);
|
||||
QCOMPARE(vs.start, 6 + 14 + 22 + 4); // kFoldCol+indent + kColType(14) + kColName(22) + 4
|
||||
QCOMPARE(vs.end, 46 + 32); // start + kColValue
|
||||
QCOMPARE(vs.start, 6 + 14 + 22 + 2); // kFoldCol+indent + kColType(14) + kColName(22) + 2*kSepWidth
|
||||
QCOMPARE(vs.end, 44 + 32); // start + kColValue
|
||||
}
|
||||
|
||||
void testColumnSpan_headerFooter() {
|
||||
@@ -386,13 +386,13 @@ private slots:
|
||||
|
||||
auto ns = rcx::nameSpanFor(lm);
|
||||
QVERIFY(ns.valid);
|
||||
QCOMPARE(ns.start, 19); // 3 + 14 + 2
|
||||
QCOMPARE(ns.end, 41); // 19 + 22 (kColName)
|
||||
QCOMPARE(ns.start, 18); // 3 + 14 + 1 (kSepWidth)
|
||||
QCOMPARE(ns.end, 40); // 18 + 22 (kColName)
|
||||
|
||||
auto vs = rcx::valueSpanFor(lm, 100);
|
||||
QVERIFY(vs.valid);
|
||||
QCOMPARE(vs.start, 43); // 19 + 22 + 2
|
||||
QCOMPARE(vs.end, 75); // 43 + 32 (kColValue)
|
||||
QCOMPARE(vs.start, 41); // 18 + 22 + 1 (kSepWidth)
|
||||
QCOMPARE(vs.end, 73); // 41 + 32 (kColValue)
|
||||
}
|
||||
|
||||
void testNodeIdJsonRoundTrip() {
|
||||
@@ -474,6 +474,38 @@ private slots:
|
||||
empty.parentId = 0;
|
||||
int ei = tree3.addNode(empty);
|
||||
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() {
|
||||
using namespace rcx;
|
||||
|
||||
@@ -94,14 +94,14 @@ private slots:
|
||||
|
||||
void testParseValueHex32() {
|
||||
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);
|
||||
QVERIFY(ok);
|
||||
QCOMPARE(b.size(), 4);
|
||||
QCOMPARE((uint8_t)b[0], (uint8_t)0xDE);
|
||||
QCOMPARE((uint8_t)b[1], (uint8_t)0xAD);
|
||||
QCOMPARE((uint8_t)b[2], (uint8_t)0xBE);
|
||||
QCOMPARE((uint8_t)b[3], (uint8_t)0xEF);
|
||||
// Value 0xDEADBEEF stored as native-endian (little-endian on x86)
|
||||
uint32_t v;
|
||||
memcpy(&v, b.data(), 4);
|
||||
QCOMPARE(v, (uint32_t)0xDEADBEEF);
|
||||
}
|
||||
|
||||
void testParseValueBool() {
|
||||
@@ -122,13 +122,12 @@ private slots:
|
||||
|
||||
void testParseValueHex0xPrefix() {
|
||||
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);
|
||||
QVERIFY(ok);
|
||||
QCOMPARE((uint8_t)b[0], (uint8_t)0xDE);
|
||||
QCOMPARE((uint8_t)b[1], (uint8_t)0xAD);
|
||||
QCOMPARE((uint8_t)b[2], (uint8_t)0xBE);
|
||||
QCOMPARE((uint8_t)b[3], (uint8_t)0xEF);
|
||||
uint32_t v32;
|
||||
memcpy(&v32, b.data(), 4);
|
||||
QCOMPARE(v32, (uint32_t)0xDEADBEEF);
|
||||
|
||||
// Pointer64 with 0x prefix
|
||||
b = fmt::parseValue(NodeKind::Pointer64, "0x0000000000400000", &ok);
|
||||
@@ -177,6 +176,44 @@ private slots:
|
||||
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() {
|
||||
// Vec2 subLine=2 (out of bounds) should return "?"
|
||||
QByteArray data(16, '\0');
|
||||
|
||||
Reference in New Issue
Block a user