mirror of
https://github.com/NohamR/Reclass.git
synced 2026-05-10 19:59:21 +00:00
feat: Vergilius-style C++ generator, struct type click fix, item view highlight fix
Rewrite C++ generator for Vergilius-style output: inline anonymous structs/unions, reference opaque types by name with struct keyword prefix, size comments, aligned offset comments, no anon_ stubs. Fix struct type name not clickable in editor headers (headerTypeNameSpan assumed "struct TYPENAME" format but named structs use bare name). Add static_assert toggle in Options > Generator, default off. Fix item view highlight bleed: patch PE_PanelItemViewRow to use theme.hover so row background matches CE_ItemViewItem.
This commit is contained in:
@@ -1468,39 +1468,35 @@ static ColumnSpan headerNameSpan(const LineMeta& lm, const QString& lineText) {
|
||||
}
|
||||
|
||||
// Type name span for struct headers (not arrays)
|
||||
// Format: "struct TYPENAME NAME {" or collapsed variants
|
||||
// For "struct NAME {" (no typename), returns invalid span
|
||||
// Named structs format as: "_MMPTE OriginalPte {" (type column = just the name)
|
||||
// Anonymous structs format as: "union {" or "struct {" (no clickable type)
|
||||
static ColumnSpan headerTypeNameSpan(const LineMeta& lm, const QString& lineText) {
|
||||
if (lm.lineKind != LineKind::Header) return {};
|
||||
if (lm.isArrayHeader) return {}; // Arrays use arrayHeaderTypeSpan instead
|
||||
if (lm.isArrayHeader) return {};
|
||||
|
||||
int ind = kFoldCol + lm.depth * 3;
|
||||
int typeW = lm.effectiveTypeW;
|
||||
int typeEnd = ind + typeW;
|
||||
|
||||
// Clamp to actual line content
|
||||
if (typeEnd > lineText.size()) typeEnd = lineText.size();
|
||||
|
||||
// Extract the type column text and check if it has a typename
|
||||
// Format: "struct" or "struct TYPENAME"
|
||||
QString typeCol = lineText.mid(ind, typeEnd - ind).trimmed();
|
||||
if (typeCol.isEmpty()) return {};
|
||||
|
||||
// Find first space (after "struct")
|
||||
int firstSpace = typeCol.indexOf(' ');
|
||||
if (firstSpace < 0) return {}; // Just "struct", no typename
|
||||
// Anonymous structs use bare keywords — not clickable
|
||||
static const QStringList kKeywords = {
|
||||
QStringLiteral("struct"), QStringLiteral("union"), QStringLiteral("class")
|
||||
};
|
||||
if (kKeywords.contains(typeCol)) return {};
|
||||
|
||||
// If there's content after "struct ", that's the typename
|
||||
QString typename_ = typeCol.mid(firstSpace + 1).trimmed();
|
||||
if (typename_.isEmpty()) return {};
|
||||
// Named struct: entire type column is the type name (e.g. "_MMPTE")
|
||||
// Find the actual text bounds within the padded column
|
||||
int start = ind;
|
||||
while (start < typeEnd && lineText[start] == ' ') start++;
|
||||
int end = start;
|
||||
while (end < typeEnd && lineText[end] != ' ') end++;
|
||||
if (end <= start) return {};
|
||||
|
||||
// Return span of the typename within the type column
|
||||
int typenameStart = ind + firstSpace + 1;
|
||||
// Find where the typename actually ends (skip padding)
|
||||
int typenameEnd = typenameStart;
|
||||
while (typenameEnd < typeEnd && lineText[typenameEnd] != ' ')
|
||||
typenameEnd++;
|
||||
|
||||
return {typenameStart, typenameEnd, true};
|
||||
return {start, end, true};
|
||||
}
|
||||
|
||||
// Type span for array headers: "int32_t[10]" in "int32_t[10] positions {"
|
||||
|
||||
@@ -68,6 +68,7 @@ struct GenContext {
|
||||
QString output;
|
||||
int padCounter = 0;
|
||||
const QHash<NodeKind, QString>* typeAliases = nullptr;
|
||||
bool emitAsserts = false;
|
||||
|
||||
QString uniquePadName() {
|
||||
return QStringLiteral("_pad%1").arg(padCounter++, 4, 16, QChar('0'));
|
||||
@@ -104,64 +105,70 @@ static QString offsetComment(int offset) {
|
||||
return QString(kCommentMarker) + QStringLiteral("// 0x%1").arg(QString::number(offset, 16).toUpper());
|
||||
}
|
||||
|
||||
static QString emitField(GenContext& ctx, const Node& node) {
|
||||
static QString indent(int depth) {
|
||||
return QString(depth * 4, ' ');
|
||||
}
|
||||
|
||||
static QString emitField(GenContext& ctx, const Node& node, int depth, int baseOffset) {
|
||||
const NodeTree& tree = ctx.tree;
|
||||
QString ind = indent(depth);
|
||||
QString name = sanitizeIdent(node.name.isEmpty()
|
||||
? QStringLiteral("field_%1").arg(node.offset, 2, 16, QChar('0'))
|
||||
: node.name);
|
||||
QString oc = offsetComment(node.offset);
|
||||
QString oc = offsetComment(baseOffset + node.offset);
|
||||
|
||||
switch (node.kind) {
|
||||
case NodeKind::Vec2:
|
||||
return QStringLiteral(" %1 %2[2];").arg(ctx.cType(NodeKind::Float), name) + oc;
|
||||
return ind + QStringLiteral("%1 %2[2];").arg(ctx.cType(NodeKind::Float), name) + oc;
|
||||
case NodeKind::Vec3:
|
||||
return QStringLiteral(" %1 %2[3];").arg(ctx.cType(NodeKind::Float), name) + oc;
|
||||
return ind + QStringLiteral("%1 %2[3];").arg(ctx.cType(NodeKind::Float), name) + oc;
|
||||
case NodeKind::Vec4:
|
||||
return QStringLiteral(" %1 %2[4];").arg(ctx.cType(NodeKind::Float), name) + oc;
|
||||
return ind + QStringLiteral("%1 %2[4];").arg(ctx.cType(NodeKind::Float), name) + oc;
|
||||
case NodeKind::Mat4x4:
|
||||
return QStringLiteral(" %1 %2[4][4];").arg(ctx.cType(NodeKind::Float), name) + oc;
|
||||
return ind + QStringLiteral("%1 %2[4][4];").arg(ctx.cType(NodeKind::Float), name) + oc;
|
||||
case NodeKind::UTF8:
|
||||
return QStringLiteral(" %1 %2[%3];").arg(ctx.cType(NodeKind::UTF8), name).arg(node.strLen) + oc;
|
||||
return ind + QStringLiteral("%1 %2[%3];").arg(ctx.cType(NodeKind::UTF8), name).arg(node.strLen) + oc;
|
||||
case NodeKind::UTF16:
|
||||
return QStringLiteral(" %1 %2[%3];").arg(ctx.cType(NodeKind::UTF16), name).arg(node.strLen) + oc;
|
||||
return ind + QStringLiteral("%1 %2[%3];").arg(ctx.cType(NodeKind::UTF16), name).arg(node.strLen) + oc;
|
||||
case NodeKind::Pointer32: {
|
||||
if (node.refId != 0) {
|
||||
int refIdx = tree.indexOfId(node.refId);
|
||||
if (refIdx >= 0) {
|
||||
QString target = ctx.structName(tree.nodes[refIdx]);
|
||||
return QStringLiteral(" %1 %2;").arg(ctx.cType(NodeKind::Pointer32), name) +
|
||||
offsetComment(node.offset).replace(QStringLiteral("//"), QStringLiteral("// -> %1*").arg(target));
|
||||
return ind + QStringLiteral("struct %1* %2;").arg(target, name) + oc;
|
||||
}
|
||||
}
|
||||
return QStringLiteral(" %1 %2;").arg(ctx.cType(NodeKind::Pointer32), name) + oc;
|
||||
return ind + QStringLiteral("%1 %2;").arg(ctx.cType(NodeKind::Pointer32), name) + oc;
|
||||
}
|
||||
case NodeKind::Pointer64: {
|
||||
if (node.refId != 0) {
|
||||
int refIdx = tree.indexOfId(node.refId);
|
||||
if (refIdx >= 0) {
|
||||
QString target = ctx.structName(tree.nodes[refIdx]);
|
||||
return QStringLiteral(" %1* %2;").arg(target, name) + oc;
|
||||
return ind + QStringLiteral("struct %1* %2;").arg(target, name) + oc;
|
||||
}
|
||||
}
|
||||
return QStringLiteral(" void* %1;").arg(name) + oc;
|
||||
return ind + QStringLiteral("void* %1;").arg(name) + oc;
|
||||
}
|
||||
case NodeKind::FuncPtr32:
|
||||
return QStringLiteral(" void (*%1)();").arg(name) + oc;
|
||||
return ind + QStringLiteral("void (*%1)();").arg(name) + oc;
|
||||
case NodeKind::FuncPtr64:
|
||||
return QStringLiteral(" void (*%1)();").arg(name) + oc;
|
||||
return ind + QStringLiteral("void (*%1)();").arg(name) + oc;
|
||||
default:
|
||||
return QStringLiteral(" %1 %2;").arg(ctx.cType(node.kind), name) + oc;
|
||||
return ind + QStringLiteral("%1 %2;").arg(ctx.cType(node.kind), name) + oc;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Emit struct body (fields + padding) ──
|
||||
// ── Emit struct body (fields + padding) — Vergilius-style ──
|
||||
|
||||
static void emitStructBody(GenContext& ctx, uint64_t structId) {
|
||||
static void emitStructBody(GenContext& ctx, uint64_t structId,
|
||||
bool isUnion, int depth, int baseOffset) {
|
||||
const NodeTree& tree = ctx.tree;
|
||||
int idx = tree.indexOfId(structId);
|
||||
if (idx < 0) return;
|
||||
|
||||
int structSize = tree.structSpan(structId, &ctx.childMap);
|
||||
QString ind = indent(depth);
|
||||
|
||||
QVector<int> children = ctx.childMap.value(structId);
|
||||
std::sort(children.begin(), children.end(), [&](int a, int b) {
|
||||
@@ -169,13 +176,12 @@ static void emitStructBody(GenContext& ctx, uint64_t structId) {
|
||||
});
|
||||
|
||||
// Helper: emit a padding/hex run as a single collapsed byte array
|
||||
auto emitPadRun = [&](int offset, int size) {
|
||||
auto emitPadRun = [&](int relOffset, int size) {
|
||||
if (size <= 0) return;
|
||||
ctx.output += QStringLiteral(" %1 %2[0x%3];%4\n")
|
||||
.arg(QStringLiteral("uint8_t"))
|
||||
ctx.output += ind + QStringLiteral("uint8_t %1[0x%2];%3\n")
|
||||
.arg(ctx.uniquePadName())
|
||||
.arg(QString::number(size, 16).toUpper())
|
||||
.arg(offsetComment(offset));
|
||||
.arg(offsetComment(baseOffset + relOffset));
|
||||
};
|
||||
|
||||
int cursor = 0;
|
||||
@@ -189,13 +195,15 @@ static void emitStructBody(GenContext& ctx, uint64_t structId) {
|
||||
else
|
||||
childSize = child.byteSize();
|
||||
|
||||
// Gap before this field
|
||||
if (child.offset > cursor)
|
||||
emitPadRun(cursor, child.offset - cursor);
|
||||
else if (child.offset < cursor)
|
||||
ctx.output += QStringLiteral(" // WARNING: overlap at offset 0x%1 (previous field ends at 0x%2)\n")
|
||||
.arg(QString::number(child.offset, 16).toUpper())
|
||||
.arg(QString::number(cursor, 16).toUpper());
|
||||
// Gap/overlap handling (skip for unions)
|
||||
if (!isUnion) {
|
||||
if (child.offset > cursor)
|
||||
emitPadRun(cursor, child.offset - cursor);
|
||||
else if (child.offset < cursor)
|
||||
ctx.output += ind + QStringLiteral("// WARNING: overlap at offset 0x%1 (previous field ends at 0x%2)\n")
|
||||
.arg(QString::number(baseOffset + child.offset, 16).toUpper())
|
||||
.arg(QString::number(baseOffset + cursor, 16).toUpper());
|
||||
}
|
||||
|
||||
// Collapse consecutive hex nodes into a single padding array
|
||||
if (isHexNode(child.kind)) {
|
||||
@@ -206,8 +214,7 @@ static void emitStructBody(GenContext& ctx, uint64_t structId) {
|
||||
const Node& next = tree.nodes[children[j]];
|
||||
if (!isHexNode(next.kind)) break;
|
||||
int nextSize = next.byteSize();
|
||||
// Allow gaps within the run (they become part of the pad)
|
||||
if (next.offset < runEnd) break; // overlap — stop merging
|
||||
if (next.offset < runEnd) break;
|
||||
runEnd = next.offset + nextSize;
|
||||
j++;
|
||||
}
|
||||
@@ -219,10 +226,31 @@ static void emitStructBody(GenContext& ctx, uint64_t structId) {
|
||||
|
||||
// Emit the field
|
||||
if (child.kind == NodeKind::Struct) {
|
||||
emitStruct(ctx, child.id);
|
||||
QString typeName = ctx.structName(child);
|
||||
QString fieldName = sanitizeIdent(child.name);
|
||||
ctx.output += QStringLiteral(" %1 %2;%3\n").arg(typeName, fieldName, offsetComment(child.offset));
|
||||
bool isAnonymous = child.structTypeName.isEmpty();
|
||||
|
||||
if (isAnonymous) {
|
||||
// Inline anonymous struct/union
|
||||
QString kw = child.resolvedClassKeyword();
|
||||
ctx.output += ind + kw + QStringLiteral("\n");
|
||||
ctx.output += ind + QStringLiteral("{\n");
|
||||
bool childIsUnion = (kw == QStringLiteral("union"));
|
||||
emitStructBody(ctx, child.id, childIsUnion, depth + 1,
|
||||
baseOffset + child.offset);
|
||||
QString fieldName = child.name.isEmpty()
|
||||
? QString() : QStringLiteral(" ") + sanitizeIdent(child.name);
|
||||
ctx.output += ind + QStringLiteral("}") + fieldName + QStringLiteral(";")
|
||||
+ offsetComment(baseOffset + child.offset) + QStringLiteral("\n");
|
||||
} else {
|
||||
// Named struct — reference by name with struct keyword prefix
|
||||
QString kw = child.resolvedClassKeyword();
|
||||
if (kw == QStringLiteral("enum") && child.enumMembers.isEmpty())
|
||||
kw = QStringLiteral("struct");
|
||||
QString typeName = sanitizeIdent(child.structTypeName);
|
||||
QString fieldName = sanitizeIdent(child.name);
|
||||
ctx.output += ind + kw + QStringLiteral(" ") + typeName
|
||||
+ QStringLiteral(" ") + fieldName + QStringLiteral(";")
|
||||
+ offsetComment(baseOffset + child.offset) + QStringLiteral("\n");
|
||||
}
|
||||
} else if (child.kind == NodeKind::Array) {
|
||||
QVector<int> arrayKids = ctx.childMap.value(child.id);
|
||||
bool hasStructChild = false;
|
||||
@@ -231,7 +259,6 @@ static void emitStructBody(GenContext& ctx, uint64_t structId) {
|
||||
for (int ak : arrayKids) {
|
||||
if (tree.nodes[ak].kind == NodeKind::Struct) {
|
||||
hasStructChild = true;
|
||||
emitStruct(ctx, tree.nodes[ak].id);
|
||||
elemTypeName = ctx.structName(tree.nodes[ak]);
|
||||
break;
|
||||
}
|
||||
@@ -239,14 +266,16 @@ static void emitStructBody(GenContext& ctx, uint64_t structId) {
|
||||
|
||||
QString fieldName = sanitizeIdent(child.name);
|
||||
if (hasStructChild && !elemTypeName.isEmpty()) {
|
||||
ctx.output += QStringLiteral(" %1 %2[%3];%4\n")
|
||||
.arg(elemTypeName, fieldName).arg(child.arrayLen).arg(offsetComment(child.offset));
|
||||
ctx.output += ind + QStringLiteral("struct %1 %2[%3];%4\n")
|
||||
.arg(elemTypeName, fieldName).arg(child.arrayLen)
|
||||
.arg(offsetComment(baseOffset + child.offset));
|
||||
} else {
|
||||
ctx.output += QStringLiteral(" %1 %2[%3];%4\n")
|
||||
.arg(ctx.cType(child.elementKind), fieldName).arg(child.arrayLen).arg(offsetComment(child.offset));
|
||||
ctx.output += ind + QStringLiteral("%1 %2[%3];%4\n")
|
||||
.arg(ctx.cType(child.elementKind), fieldName).arg(child.arrayLen)
|
||||
.arg(offsetComment(baseOffset + child.offset));
|
||||
}
|
||||
} else {
|
||||
ctx.output += emitField(ctx, child) + QStringLiteral("\n");
|
||||
ctx.output += emitField(ctx, child, depth, baseOffset) + QStringLiteral("\n");
|
||||
}
|
||||
|
||||
int childEnd = child.offset + childSize;
|
||||
@@ -254,12 +283,12 @@ static void emitStructBody(GenContext& ctx, uint64_t structId) {
|
||||
i++;
|
||||
}
|
||||
|
||||
// Tail padding
|
||||
if (cursor < structSize)
|
||||
// Tail padding (skip for unions)
|
||||
if (!isUnion && cursor < structSize)
|
||||
emitPadRun(cursor, structSize - cursor);
|
||||
}
|
||||
|
||||
// ── Emit a complete struct definition ──
|
||||
// ── Emit a complete top-level struct definition (Vergilius-style) ──
|
||||
|
||||
static void emitStruct(GenContext& ctx, uint64_t structId) {
|
||||
if (ctx.emittedIds.contains(structId)) return;
|
||||
@@ -275,19 +304,12 @@ static void emitStruct(GenContext& ctx, uint64_t structId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// For arrays, we don't emit a top-level struct — the array itself
|
||||
// is a field inside its parent. But we do emit struct element types.
|
||||
if (node.kind == NodeKind::Array) {
|
||||
QVector<int> kids = ctx.childMap.value(structId);
|
||||
for (int ki : kids) {
|
||||
if (ctx.tree.nodes[ki].kind == NodeKind::Struct)
|
||||
emitStruct(ctx, ctx.tree.nodes[ki].id);
|
||||
}
|
||||
ctx.visiting.remove(structId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Deduplicate by struct type name (different nodes may share the same type)
|
||||
// Deduplicate by struct type name
|
||||
QString typeName = ctx.structName(node);
|
||||
if (ctx.emittedTypeNames.contains(typeName)) {
|
||||
ctx.emittedIds.insert(structId);
|
||||
@@ -295,34 +317,6 @@ static void emitStruct(GenContext& ctx, uint64_t structId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Emit nested struct types first (dependency order)
|
||||
QVector<int> children = ctx.childMap.value(structId);
|
||||
for (int ci : children) {
|
||||
const Node& child = ctx.tree.nodes[ci];
|
||||
if (child.kind == NodeKind::Struct)
|
||||
emitStruct(ctx, child.id);
|
||||
else if (child.kind == NodeKind::Array) {
|
||||
QVector<int> arrayKids = ctx.childMap.value(child.id);
|
||||
for (int ak : arrayKids) {
|
||||
if (ctx.tree.nodes[ak].kind == NodeKind::Struct)
|
||||
emitStruct(ctx, ctx.tree.nodes[ak].id);
|
||||
}
|
||||
}
|
||||
// Forward-declare pointer target types if they're outside this subtree
|
||||
if (child.kind == NodeKind::Pointer64 && child.refId != 0) {
|
||||
int refIdx = ctx.tree.indexOfId(child.refId);
|
||||
if (refIdx >= 0 && !ctx.emittedIds.contains(child.refId)
|
||||
&& !ctx.forwardDeclared.contains(child.refId)) {
|
||||
QString fwdName = ctx.structName(ctx.tree.nodes[refIdx]);
|
||||
QString fwdKw = ctx.tree.nodes[refIdx].resolvedClassKeyword();
|
||||
if (fwdKw == QStringLiteral("enum") && ctx.tree.nodes[refIdx].enumMembers.isEmpty())
|
||||
fwdKw = QStringLiteral("struct");
|
||||
ctx.output += QStringLiteral("%1 %2;\n").arg(fwdKw, fwdName);
|
||||
ctx.forwardDeclared.insert(child.refId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx.emittedIds.insert(structId);
|
||||
ctx.emittedTypeNames.insert(typeName);
|
||||
int structSize = ctx.tree.structSpan(structId, &ctx.childMap);
|
||||
@@ -342,15 +336,21 @@ static void emitStruct(GenContext& ctx, uint64_t structId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (kw == QStringLiteral("enum")) kw = QStringLiteral("struct"); // enum without members: fallback
|
||||
ctx.output += QStringLiteral("%1 %2 {\n").arg(kw, typeName);
|
||||
if (kw == QStringLiteral("enum")) kw = QStringLiteral("struct");
|
||||
|
||||
emitStructBody(ctx, structId);
|
||||
// Size comment (Vergilius-style)
|
||||
ctx.output += QStringLiteral("//0x%1 bytes (sizeof)\n")
|
||||
.arg(QString::number(structSize, 16).toUpper());
|
||||
ctx.output += kw + QStringLiteral(" ") + typeName + QStringLiteral("\n{\n");
|
||||
|
||||
emitStructBody(ctx, structId, kw == QStringLiteral("union"), 1, 0);
|
||||
|
||||
ctx.output += QStringLiteral("};\n");
|
||||
ctx.output += QStringLiteral("static_assert(sizeof(%1) == 0x%2, \"Size mismatch for %1\");\n\n")
|
||||
.arg(typeName)
|
||||
.arg(QString::number(structSize, 16).toUpper());
|
||||
if (ctx.emitAsserts)
|
||||
ctx.output += QStringLiteral("static_assert(sizeof(%1) == 0x%2, \"Size mismatch for %1\");\n")
|
||||
.arg(typeName)
|
||||
.arg(QString::number(structSize, 16).toUpper());
|
||||
ctx.output += QStringLiteral("\n");
|
||||
|
||||
ctx.visiting.remove(structId);
|
||||
}
|
||||
@@ -404,14 +404,15 @@ static QString alignComments(const QString& raw) {
|
||||
// ── Public API ──
|
||||
|
||||
QString renderCpp(const NodeTree& tree, uint64_t rootStructId,
|
||||
const QHash<NodeKind, QString>* typeAliases) {
|
||||
const QHash<NodeKind, QString>* typeAliases,
|
||||
bool emitAsserts) {
|
||||
int idx = tree.indexOfId(rootStructId);
|
||||
if (idx < 0) return {};
|
||||
|
||||
const Node& root = tree.nodes[idx];
|
||||
if (root.kind != NodeKind::Struct) return {};
|
||||
|
||||
GenContext ctx{tree, buildChildMap(tree), {}, {}, {}, {}, {}, 0, typeAliases};
|
||||
GenContext ctx{tree, buildChildMap(tree), {}, {}, {}, {}, {}, 0, typeAliases, emitAsserts};
|
||||
|
||||
ctx.output += QStringLiteral("#pragma once\n\n");
|
||||
|
||||
@@ -421,8 +422,9 @@ QString renderCpp(const NodeTree& tree, uint64_t rootStructId,
|
||||
}
|
||||
|
||||
QString renderCppAll(const NodeTree& tree,
|
||||
const QHash<NodeKind, QString>* typeAliases) {
|
||||
GenContext ctx{tree, buildChildMap(tree), {}, {}, {}, {}, {}, 0, typeAliases};
|
||||
const QHash<NodeKind, QString>* typeAliases,
|
||||
bool emitAsserts) {
|
||||
GenContext ctx{tree, buildChildMap(tree), {}, {}, {}, {}, {}, 0, typeAliases, emitAsserts};
|
||||
|
||||
ctx.output += QStringLiteral("#pragma once\n\n");
|
||||
|
||||
|
||||
@@ -9,11 +9,13 @@ namespace rcx {
|
||||
// Generate C++ struct definitions for a single root struct and all
|
||||
// nested/referenced types reachable from it.
|
||||
QString renderCpp(const NodeTree& tree, uint64_t rootStructId,
|
||||
const QHash<NodeKind, QString>* typeAliases = nullptr);
|
||||
const QHash<NodeKind, QString>* typeAliases = nullptr,
|
||||
bool emitAsserts = false);
|
||||
|
||||
// Generate C++ struct definitions for every root-level struct (full SDK).
|
||||
QString renderCppAll(const NodeTree& tree,
|
||||
const QHash<NodeKind, QString>* typeAliases = nullptr);
|
||||
const QHash<NodeKind, QString>* typeAliases = nullptr,
|
||||
bool emitAsserts = false);
|
||||
|
||||
// Null generator placeholder (returns empty string).
|
||||
QString renderNull(const NodeTree& tree, uint64_t rootStructId);
|
||||
|
||||
22
src/main.cpp
22
src/main.cpp
@@ -267,6 +267,16 @@ public:
|
||||
// Transparent menu bar background (no CSS needed)
|
||||
if (elem == PE_PanelMenuBar)
|
||||
return;
|
||||
// Item-view row background — patch Highlight so the row bg matches CE_ItemViewItem
|
||||
if (elem == PE_PanelItemViewRow) {
|
||||
if (auto* vi = qstyleoption_cast<const QStyleOptionViewItem*>(opt)) {
|
||||
QStyleOptionViewItem patched = *vi;
|
||||
patched.palette.setColor(QPalette::Highlight,
|
||||
vi->palette.color(QPalette::Mid));
|
||||
QProxyStyle::drawPrimitive(elem, &patched, p, w);
|
||||
return;
|
||||
}
|
||||
}
|
||||
QProxyStyle::drawPrimitive(elem, opt, p, w);
|
||||
}
|
||||
void drawControl(ControlElement element, const QStyleOption* opt,
|
||||
@@ -1804,6 +1814,7 @@ void MainWindow::showOptionsDialog() {
|
||||
current.safeMode = QSettings("Reclass", "Reclass").value("safeMode", false).toBool();
|
||||
current.autoStartMcp = QSettings("Reclass", "Reclass").value("autoStartMcp", true).toBool();
|
||||
current.refreshMs = QSettings("Reclass", "Reclass").value("refreshMs", 660).toInt();
|
||||
current.generatorAsserts = QSettings("Reclass", "Reclass").value("generatorAsserts", false).toBool();
|
||||
|
||||
OptionsDialog dlg(current, this);
|
||||
if (dlg.exec() != QDialog::Accepted) return; // OptionsDialog doesn't apply anything. Only apply on OK
|
||||
@@ -1837,6 +1848,9 @@ void MainWindow::showOptionsDialog() {
|
||||
for (auto& tab : m_tabs)
|
||||
tab.ctrl->setRefreshInterval(r.refreshMs);
|
||||
}
|
||||
|
||||
if (r.generatorAsserts != current.generatorAsserts)
|
||||
QSettings("Reclass", "Reclass").setValue("generatorAsserts", r.generatorAsserts);
|
||||
}
|
||||
|
||||
void MainWindow::setEditorFont(const QString& fontName) {
|
||||
@@ -2023,11 +2037,12 @@ void MainWindow::updateRenderedView(TabState& tab, SplitPane& pane) {
|
||||
// Generate text
|
||||
const QHash<NodeKind, QString>* aliases =
|
||||
tab.doc->typeAliases.isEmpty() ? nullptr : &tab.doc->typeAliases;
|
||||
bool asserts = QSettings("Reclass", "Reclass").value("generatorAsserts", false).toBool();
|
||||
QString text;
|
||||
if (rootId != 0)
|
||||
text = renderCpp(tab.doc->tree, rootId, aliases);
|
||||
text = renderCpp(tab.doc->tree, rootId, aliases, asserts);
|
||||
else
|
||||
text = renderCppAll(tab.doc->tree, aliases);
|
||||
text = renderCppAll(tab.doc->tree, aliases, asserts);
|
||||
|
||||
// Scroll restoration: save if same root, reset if different
|
||||
int restoreLine = 0;
|
||||
@@ -2071,7 +2086,8 @@ void MainWindow::exportCpp() {
|
||||
|
||||
const QHash<NodeKind, QString>* aliases =
|
||||
tab->doc->typeAliases.isEmpty() ? nullptr : &tab->doc->typeAliases;
|
||||
QString text = renderCppAll(tab->doc->tree, aliases);
|
||||
bool asserts = QSettings("Reclass", "Reclass").value("generatorAsserts", false).toBool();
|
||||
QString text = renderCppAll(tab->doc->tree, aliases, asserts);
|
||||
QFile file(path);
|
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
QMessageBox::warning(this, "Export Failed",
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "generator.h"
|
||||
#include "mainwindow.h"
|
||||
#include <QCoreApplication>
|
||||
#include <QSettings>
|
||||
#include <QDebug>
|
||||
#include <cstring>
|
||||
|
||||
@@ -1094,15 +1095,16 @@ QJsonObject McpBridge::toolUiAction(const QJsonObject& args) {
|
||||
if (action == "export_cpp") {
|
||||
if (!doc) return makeTextResult("No active tab", true);
|
||||
const QHash<NodeKind, QString>* aliases = doc->typeAliases.isEmpty() ? nullptr : &doc->typeAliases;
|
||||
bool asserts = QSettings("Reclass", "Reclass").value("generatorAsserts", false).toBool();
|
||||
QString code;
|
||||
if (!nodeIdStr.isEmpty()) {
|
||||
// Per-struct export
|
||||
uint64_t nid = nodeIdStr.toULongLong();
|
||||
code = renderCpp(doc->tree, nid, aliases);
|
||||
code = renderCpp(doc->tree, nid, aliases, asserts);
|
||||
if (code.isEmpty())
|
||||
return makeTextResult("Node not found or not a struct: " + nodeIdStr, true);
|
||||
} else {
|
||||
code = renderCppAll(doc->tree, aliases);
|
||||
code = renderCppAll(doc->tree, aliases, asserts);
|
||||
}
|
||||
// Truncate if too large (64 KB limit)
|
||||
if (code.size() > 65536) {
|
||||
|
||||
@@ -170,6 +170,14 @@ OptionsDialog::OptionsDialog(const OptionsResult& current, QWidget* parent)
|
||||
auto* generatorLayout = new QVBoxLayout(generatorPage);
|
||||
generatorLayout->setContentsMargins(0, 0, 0, 0);
|
||||
generatorLayout->setSpacing(8);
|
||||
|
||||
auto* cppGroup = new QGroupBox("C++ Header");
|
||||
auto* cppLayout = new QVBoxLayout(cppGroup);
|
||||
m_assertCheck = new QCheckBox("Emit static_assert size checks");
|
||||
m_assertCheck->setChecked(current.generatorAsserts);
|
||||
cppLayout->addWidget(m_assertCheck);
|
||||
generatorLayout->addWidget(cppGroup);
|
||||
|
||||
generatorLayout->addStretch();
|
||||
|
||||
m_pages->addWidget(generatorPage); // index 2
|
||||
@@ -208,6 +216,7 @@ OptionsResult OptionsDialog::result() const {
|
||||
r.safeMode = m_safeModeCheck->isChecked();
|
||||
r.autoStartMcp = m_autoMcpCheck->isChecked();
|
||||
r.refreshMs = m_refreshSpin->value();
|
||||
r.generatorAsserts = m_assertCheck->isChecked();
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ struct OptionsResult {
|
||||
bool safeMode = false;
|
||||
bool autoStartMcp = true;
|
||||
int refreshMs = 660;
|
||||
bool generatorAsserts = false;
|
||||
};
|
||||
|
||||
class OptionsDialog : public QDialog {
|
||||
@@ -41,6 +42,7 @@ private:
|
||||
QCheckBox* m_safeModeCheck = nullptr;
|
||||
QCheckBox* m_autoMcpCheck = nullptr;
|
||||
QSpinBox* m_refreshSpin = nullptr;
|
||||
QCheckBox* m_assertCheck = nullptr;
|
||||
|
||||
// searchable keywords per leaf tree item
|
||||
QHash<QTreeWidgetItem*, QStringList> m_pageKeywords;
|
||||
|
||||
@@ -2514,6 +2514,48 @@ private slots:
|
||||
<< QString("gapRight=%1 gapBottom=%2 (font-independent)")
|
||||
.arg(gapR1).arg(gapB1);
|
||||
}
|
||||
|
||||
// ── Test: hovering struct type name shows PointingHand cursor ──
|
||||
// Regression: headerTypeNameSpan returned invalid for named structs
|
||||
// because it assumed "struct TYPENAME" format, but named structs are
|
||||
// formatted as just "TYPENAME" (e.g. "_STRING64 CSDVersion").
|
||||
void testStructTypeClickable() {
|
||||
m_editor->applyDocument(m_result);
|
||||
QApplication::processEvents();
|
||||
|
||||
// Find a named struct header (e.g. _STRING64 CSDVersion from makeTestTree)
|
||||
int headerLine = -1;
|
||||
for (int i = 0; i < m_result.meta.size(); i++) {
|
||||
const auto& lm = m_result.meta[i];
|
||||
if (lm.lineKind == LineKind::Header && lm.foldHead
|
||||
&& lm.nodeKind == NodeKind::Struct && !lm.isArrayHeader) {
|
||||
headerLine = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
QVERIFY2(headerLine >= 0, "Should have a struct header");
|
||||
|
||||
const LineMeta* lm = m_editor->metaForLine(headerLine);
|
||||
QVERIFY(lm);
|
||||
|
||||
// Scroll to ensure line is visible
|
||||
m_editor->scintilla()->SendScintilla(
|
||||
QsciScintillaBase::SCI_ENSUREVISIBLE, (unsigned long)headerLine);
|
||||
m_editor->scintilla()->SendScintilla(
|
||||
QsciScintillaBase::SCI_GOTOLINE, (unsigned long)headerLine);
|
||||
QApplication::processEvents();
|
||||
|
||||
// The type column starts at kFoldCol + depth*3
|
||||
int typeStart = 3 + lm->depth * 3; // kFoldCol = 3
|
||||
|
||||
// Hover over type column — should show PointingHandCursor
|
||||
// (Before fix: showed ArrowCursor because headerTypeNameSpan returned invalid)
|
||||
QPoint typePos = colToViewport(m_editor->scintilla(), headerLine, typeStart + 1);
|
||||
QVERIFY2(typePos.y() > 0, "Header line should be visible");
|
||||
sendMouseMove(m_editor->scintilla()->viewport(), typePos);
|
||||
QApplication::processEvents();
|
||||
QCOMPARE(viewportCursor(m_editor), Qt::PointingHandCursor);
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(TestEditor)
|
||||
|
||||
@@ -46,27 +46,37 @@ private:
|
||||
|
||||
private slots:
|
||||
|
||||
// ── Basic struct generation ──
|
||||
// ── Basic struct generation (Vergilius-style) ──
|
||||
|
||||
void testSimpleStruct() {
|
||||
auto tree = makeSimpleStruct();
|
||||
uint64_t rootId = tree.nodes[0].id;
|
||||
QString result = rcx::renderCpp(tree, rootId);
|
||||
QString result = rcx::renderCpp(tree, rootId, nullptr, true);
|
||||
|
||||
// Header
|
||||
QVERIFY(result.contains("#pragma once"));
|
||||
QVERIFY(!result.contains("#include <cstdint>"));
|
||||
QVERIFY(!result.contains("#pragma pack"));
|
||||
|
||||
// Struct definition
|
||||
QVERIFY(result.contains("struct Player {"));
|
||||
// Size comment (Vergilius-style)
|
||||
QVERIFY(result.contains("//0x10 bytes (sizeof)"));
|
||||
|
||||
// Struct definition (brace on new line)
|
||||
QVERIFY(result.contains("struct Player\n{"));
|
||||
QVERIFY(result.contains("int32_t health;"));
|
||||
QVERIFY(result.contains("float speed;"));
|
||||
QVERIFY(result.contains("uint64_t id;"));
|
||||
QVERIFY(result.contains("};"));
|
||||
|
||||
// static_assert - struct is 16 bytes (0+4 + 4+4 + 8+8 = 16)
|
||||
// Offset comments
|
||||
QVERIFY(result.contains("// 0x0"));
|
||||
QVERIFY(result.contains("// 0x4"));
|
||||
QVERIFY(result.contains("// 0x8"));
|
||||
|
||||
// static_assert
|
||||
QVERIFY(result.contains("static_assert(sizeof(Player) == 0x10"));
|
||||
|
||||
// Without emitAsserts, static_assert should not appear
|
||||
QString noAsserts = rcx::renderCpp(tree, rootId);
|
||||
QVERIFY(!noAsserts.contains("static_assert"));
|
||||
}
|
||||
|
||||
// ── Padding gap detection ──
|
||||
@@ -134,7 +144,7 @@ private slots:
|
||||
f2.offset = 16;
|
||||
tree.addNode(f2);
|
||||
|
||||
QString result = rcx::renderCpp(tree, rootId);
|
||||
QString result = rcx::renderCpp(tree, rootId, nullptr, true);
|
||||
|
||||
// Gap between offset 1 and 16 = 15 bytes padding
|
||||
QVERIFY(result.contains("[0xF]"));
|
||||
@@ -175,7 +185,47 @@ private slots:
|
||||
QVERIFY(result.contains("WARNING: overlap"));
|
||||
}
|
||||
|
||||
// ── Nested struct ──
|
||||
// ── Union members should NOT produce overlap warnings ──
|
||||
|
||||
void testUnionNoOverlapWarning() {
|
||||
rcx::NodeTree tree;
|
||||
rcx::Node root;
|
||||
root.kind = rcx::NodeKind::Struct;
|
||||
root.name = "TestUnion";
|
||||
root.structTypeName = "TestUnion";
|
||||
root.classKeyword = "union";
|
||||
root.parentId = 0;
|
||||
int ri = tree.addNode(root);
|
||||
uint64_t rootId = tree.nodes[ri].id;
|
||||
|
||||
// Two union members at offset 0
|
||||
rcx::Node f1;
|
||||
f1.kind = rcx::NodeKind::UInt64;
|
||||
f1.name = "wide";
|
||||
f1.parentId = rootId;
|
||||
f1.offset = 0;
|
||||
tree.addNode(f1);
|
||||
|
||||
rcx::Node f2;
|
||||
f2.kind = rcx::NodeKind::UInt32;
|
||||
f2.name = "narrow";
|
||||
f2.parentId = rootId;
|
||||
f2.offset = 0;
|
||||
tree.addNode(f2);
|
||||
|
||||
QString result = rcx::renderCpp(tree, rootId);
|
||||
|
||||
// Vergilius-style: union keyword, brace on new line
|
||||
QVERIFY(result.contains("union TestUnion\n{"));
|
||||
QVERIFY(result.contains("uint64_t wide;"));
|
||||
QVERIFY(result.contains("uint32_t narrow;"));
|
||||
// Union members overlap by design — no warning
|
||||
QVERIFY(!result.contains("WARNING"));
|
||||
// No padding in unions
|
||||
QVERIFY(!result.contains("_pad"));
|
||||
}
|
||||
|
||||
// ── Nested struct: named sub-type referenced by name ──
|
||||
|
||||
void testNestedStruct() {
|
||||
rcx::NodeTree tree;
|
||||
@@ -222,23 +272,14 @@ private slots:
|
||||
f2.offset = 8;
|
||||
tree.addNode(f2);
|
||||
|
||||
QString result = rcx::renderCpp(tree, outerId);
|
||||
QString result = rcx::renderCpp(tree, outerId, nullptr, true);
|
||||
|
||||
// Inner struct should be defined before outer
|
||||
int innerPos = result.indexOf("struct Vec2f {");
|
||||
int outerPos = result.indexOf("struct Outer {");
|
||||
QVERIFY(innerPos >= 0);
|
||||
QVERIFY(outerPos >= 0);
|
||||
QVERIFY(innerPos < outerPos);
|
||||
|
||||
// Inner struct fields
|
||||
QVERIFY(result.contains("float x;"));
|
||||
QVERIFY(result.contains("float y;"));
|
||||
QVERIFY(result.contains("static_assert(sizeof(Vec2f) == 0x8"));
|
||||
|
||||
// Outer struct uses inner type
|
||||
QVERIFY(result.contains("Vec2f pos;"));
|
||||
// Vergilius-style: named sub-types referenced by name with struct prefix
|
||||
// No separate top-level definition for Vec2f in renderCpp
|
||||
QVERIFY(result.contains("struct Outer\n{"));
|
||||
QVERIFY(result.contains("struct Vec2f pos;"));
|
||||
QVERIFY(result.contains("int32_t score;"));
|
||||
QVERIFY(result.contains("static_assert(sizeof(Outer) == 0xC"));
|
||||
}
|
||||
|
||||
// ── Primitive array ──
|
||||
@@ -325,15 +366,12 @@ private slots:
|
||||
|
||||
QString result = rcx::renderCpp(tree, mainId);
|
||||
|
||||
// ptr64 with target → real C++ pointer
|
||||
QVERIFY(result.contains("TargetData* pTarget;"));
|
||||
// Vergilius-style: struct prefix on pointer targets
|
||||
QVERIFY(result.contains("struct TargetData* pTarget;"));
|
||||
// ptr64 without target → void*
|
||||
QVERIFY(result.contains("void* pVoid;"));
|
||||
// ptr32 with target → uint32_t with comment
|
||||
QVERIFY(result.contains("uint32_t pTarget32;"));
|
||||
QVERIFY(result.contains("-> TargetData*"));
|
||||
// Forward declaration for TargetData
|
||||
QVERIFY(result.contains("struct TargetData;"));
|
||||
// ptr32 with target → struct X* (Vergilius-style, no forward decl needed)
|
||||
QVERIFY(result.contains("struct TargetData* pTarget32;"));
|
||||
}
|
||||
|
||||
// ── Vector and matrix types ──
|
||||
@@ -457,10 +495,11 @@ private slots:
|
||||
bf.offset = 0;
|
||||
tree.addNode(bf);
|
||||
|
||||
QString result = rcx::renderCppAll(tree);
|
||||
QString result = rcx::renderCppAll(tree, nullptr, true);
|
||||
|
||||
QVERIFY(result.contains("struct StructA {"));
|
||||
QVERIFY(result.contains("struct StructB {"));
|
||||
// Vergilius-style: brace on new line
|
||||
QVERIFY(result.contains("struct StructA\n{"));
|
||||
QVERIFY(result.contains("struct StructB\n{"));
|
||||
QVERIFY(result.contains("uint32_t valueA;"));
|
||||
QVERIFY(result.contains("uint64_t valueB;"));
|
||||
QVERIFY(result.contains("static_assert(sizeof(StructA) == 0x4"));
|
||||
@@ -508,9 +547,9 @@ private slots:
|
||||
root.parentId = 0;
|
||||
tree.addNode(root);
|
||||
|
||||
QString result = rcx::renderCpp(tree, tree.nodes[0].id);
|
||||
QString result = rcx::renderCpp(tree, tree.nodes[0].id, nullptr, true);
|
||||
|
||||
QVERIFY(result.contains("struct Empty {"));
|
||||
QVERIFY(result.contains("struct Empty\n{"));
|
||||
QVERIFY(result.contains("};"));
|
||||
QVERIFY(result.contains("static_assert(sizeof(Empty) == 0x0"));
|
||||
}
|
||||
@@ -537,7 +576,7 @@ private slots:
|
||||
QString result = rcx::renderCpp(tree, rootId);
|
||||
|
||||
// Spaces and dashes should be replaced with underscores
|
||||
QVERIFY(result.contains("struct my_struct_name {"));
|
||||
QVERIFY(result.contains("struct my_struct_name\n{"));
|
||||
QVERIFY(result.contains("uint32_t field_with_spaces;"));
|
||||
}
|
||||
|
||||
@@ -546,7 +585,7 @@ private slots:
|
||||
void testExportToFile() {
|
||||
auto tree = makeSimpleStruct();
|
||||
uint64_t rootId = tree.nodes[0].id;
|
||||
QString text = rcx::renderCpp(tree, rootId);
|
||||
QString text = rcx::renderCpp(tree, rootId, nullptr, true);
|
||||
|
||||
QTemporaryFile tmpFile;
|
||||
tmpFile.setAutoRemove(true);
|
||||
@@ -561,7 +600,7 @@ private slots:
|
||||
|
||||
QString readStr = QString::fromUtf8(readBack);
|
||||
QVERIFY(readStr.contains("#pragma once"));
|
||||
QVERIFY(readStr.contains("struct Player {"));
|
||||
QVERIFY(readStr.contains("struct Player\n{"));
|
||||
QVERIFY(readStr.contains("static_assert"));
|
||||
}
|
||||
|
||||
@@ -582,7 +621,7 @@ private slots:
|
||||
QVERIFY(!result.contains("struct "));
|
||||
}
|
||||
|
||||
// ── Deeply nested structs ──
|
||||
// ── Deeply nested structs: referenced by name ──
|
||||
|
||||
void testDeeplyNested() {
|
||||
rcx::NodeTree tree;
|
||||
@@ -623,20 +662,101 @@ private slots:
|
||||
|
||||
QString result = rcx::renderCpp(tree, aId);
|
||||
|
||||
// TypeC defined first, then TypeB, then TypeA
|
||||
int cPos = result.indexOf("struct TypeC {");
|
||||
int bPos = result.indexOf("struct TypeB {");
|
||||
int aPos = result.indexOf("struct TypeA {");
|
||||
QVERIFY(cPos >= 0);
|
||||
QVERIFY(bPos >= 0);
|
||||
QVERIFY(aPos >= 0);
|
||||
QVERIFY(cPos < bPos);
|
||||
QVERIFY(bPos < aPos);
|
||||
// Vergilius-style: named sub-types referenced by name with struct prefix
|
||||
// Only the root type gets a top-level definition
|
||||
QVERIFY(result.contains("struct TypeA\n{"));
|
||||
QVERIFY(result.contains("struct TypeB b;"));
|
||||
}
|
||||
|
||||
// TypeA contains TypeB, TypeB contains TypeC
|
||||
QVERIFY(result.contains("TypeB b;"));
|
||||
QVERIFY(result.contains("TypeC c;"));
|
||||
QVERIFY(result.contains("uint8_t val;"));
|
||||
// ── Inline anonymous struct/union ──
|
||||
|
||||
void testInlineAnonymousStruct() {
|
||||
rcx::NodeTree tree;
|
||||
|
||||
rcx::Node root;
|
||||
root.kind = rcx::NodeKind::Struct;
|
||||
root.name = "_MMPFN";
|
||||
root.structTypeName = "_MMPFN";
|
||||
root.parentId = 0;
|
||||
int ri = tree.addNode(root);
|
||||
uint64_t rootId = tree.nodes[ri].id;
|
||||
|
||||
// Anonymous union at offset 0 (no structTypeName)
|
||||
rcx::Node anonUnion;
|
||||
anonUnion.kind = rcx::NodeKind::Struct;
|
||||
anonUnion.name = "";
|
||||
anonUnion.structTypeName = "";
|
||||
anonUnion.classKeyword = "union";
|
||||
anonUnion.parentId = rootId;
|
||||
anonUnion.offset = 0;
|
||||
int ui = tree.addNode(anonUnion);
|
||||
uint64_t unionId = tree.nodes[ui].id;
|
||||
|
||||
// Union member 1: named struct reference
|
||||
rcx::Node listEntry;
|
||||
listEntry.kind = rcx::NodeKind::Struct;
|
||||
listEntry.name = "ListEntry";
|
||||
listEntry.structTypeName = "_LIST_ENTRY";
|
||||
listEntry.parentId = unionId;
|
||||
listEntry.offset = 0;
|
||||
tree.addNode(listEntry);
|
||||
|
||||
// Union member 2: a simple field
|
||||
rcx::Node flags;
|
||||
flags.kind = rcx::NodeKind::UInt64;
|
||||
flags.name = "Flags";
|
||||
flags.parentId = unionId;
|
||||
flags.offset = 0;
|
||||
tree.addNode(flags);
|
||||
|
||||
// Field after the anonymous union
|
||||
rcx::Node pfn;
|
||||
pfn.kind = rcx::NodeKind::UInt64;
|
||||
pfn.name = "PfnCount";
|
||||
pfn.parentId = rootId;
|
||||
pfn.offset = 0x10;
|
||||
tree.addNode(pfn);
|
||||
|
||||
QString result = rcx::renderCpp(tree, rootId);
|
||||
|
||||
// Anonymous union should be inlined, not a top-level anon_XXXX
|
||||
QVERIFY(!result.contains("anon_"));
|
||||
QVERIFY(result.contains("union\n {"));
|
||||
QVERIFY(result.contains("struct _LIST_ENTRY ListEntry;"));
|
||||
QVERIFY(result.contains("uint64_t Flags;"));
|
||||
QVERIFY(result.contains("};"));
|
||||
QVERIFY(result.contains("uint64_t PfnCount;"));
|
||||
}
|
||||
|
||||
// ── Opaque types: no stub definition ──
|
||||
|
||||
void testOpaqueTypeNoStub() {
|
||||
rcx::NodeTree tree;
|
||||
|
||||
rcx::Node root;
|
||||
root.kind = rcx::NodeKind::Struct;
|
||||
root.name = "Container";
|
||||
root.structTypeName = "Container";
|
||||
root.parentId = 0;
|
||||
int ri = tree.addNode(root);
|
||||
uint64_t rootId = tree.nodes[ri].id;
|
||||
|
||||
// Named struct child with no children of its own (opaque reference)
|
||||
rcx::Node opaque;
|
||||
opaque.kind = rcx::NodeKind::Struct;
|
||||
opaque.name = "entry";
|
||||
opaque.structTypeName = "_LIST_ENTRY";
|
||||
opaque.parentId = rootId;
|
||||
opaque.offset = 0;
|
||||
tree.addNode(opaque);
|
||||
|
||||
QString result = rcx::renderCpp(tree, rootId);
|
||||
|
||||
// Should reference by name with struct prefix, no stub body
|
||||
QVERIFY(result.contains("struct _LIST_ENTRY entry;"));
|
||||
// Should NOT have a separate _LIST_ENTRY definition with padding
|
||||
QVERIFY(!result.contains("struct _LIST_ENTRY\n{"));
|
||||
QVERIFY(!result.contains("uint8_t _pad"));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user