Files
amacocian 97fed608d2 Update GWCA (#1484)
* Improve GWCA bindings generator based on headers

* Move all structs to gwca

* Progress

* Complete parser work

* More generator adjustments

* GWCA fixes

* GWCA improvements

* GWCA fixes

* GWCA fixes

* GWCA fixes

* GWCA fixes

* Fix API usage of GWCA

* Update GWCA

---------

Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2026-02-26 14:13:40 -08:00

864 lines
35 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using static Daybreak.Generators.GenerateGWCABindings;
namespace Daybreak.Generators;
internal static class CppHeaderParser
{
// ════════════════════════════════════════════════════════════════
// Regex patterns for enum parsing
// ════════════════════════════════════════════════════════════════
// Matches: enum class Name : Type {
// Matches: enum class Name {
// Matches: enum Name : Type {
// Matches: enum {
private static readonly Regex EnumStartRegex = new(
@"^\s*enum\s+(?:class\s+)?(?:(\w+)\s*)?(?::\s*(\w+)\s*)?\{",
RegexOptions.Compiled);
// Matches: enum class Name : Type (brace on next line)
// Matches: enum class Name (brace on next line)
// Does NOT match forward declarations ending with ;
private static readonly Regex EnumNobraceRegex = new(
@"^\s*enum\s+(?:class\s+)?(?:(\w+)\s*)?(?::\s*(\w+)\s*)?$",
RegexOptions.Compiled);
// ════════════════════════════════════════════════════════════════
// Regex patterns for struct parsing
// ════════════════════════════════════════════════════════════════
// Matches: struct Name { or struct Name : public Base { or struct Name : Base {
// Captures: Name, optional BaseType
private static readonly Regex StructStartRegex = new(
@"^\s*struct\s+(\w+)\s*(?::\s*(?:public\s+)?(\w+(?:::\w+)*)\s*)?\{",
RegexOptions.Compiled);
// Matches: struct Name : public Base (brace on next line)
private static readonly Regex StructNobraceRegex = new(
@"^\s*struct\s+(\w+)\s*(?::\s*(?:public\s+)?(\w+(?:::\w+)*)\s*)?$",
RegexOptions.Compiled);
// Matches field with offset comment: /* +h0024 */ Type field;
private static readonly Regex FieldWithOffsetRegex = new(
@"^\s*/\*\s*\+h([0-9A-Fa-f]{4})\s*\*/\s*(.+?)\s*;\s*(?://\s*(.*))?$",
RegexOptions.Compiled);
// Matches field without offset comment: Type field;
// Also handles inline initializers like = value or {} or {value}
private static readonly Regex FieldNoOffsetRegex = new(
@"^\s*(?!(?:inline|static|virtual|GWCA_API|typedef|using|enum|struct|union|return|if|for|while|switch|\[\[|\}))([A-Za-z_][\w:*&<>\s,]+?)\s+(\w+)(?:\s*\[\s*([^\]]+)\s*\])?(?:\s*\{[^}]*\})?(?:\s*=\s*[^;]+)?\s*;\s*(?://\s*(.*))?$",
RegexOptions.Compiled);
// Matches static_assert(sizeof(Name) == 0xHEX or decimal)
private static readonly Regex SizeofAssertRegex = new(
@"static_assert\s*\(\s*sizeof\s*\(\s*(\w+)\s*\)\s*==\s*(0x[0-9A-Fa-f]+|\d+)",
RegexOptions.Compiled);
// ════════════════════════════════════════════════════════════════
// Other patterns
// ════════════════════════════════════════════════════════════════
// Matches: constexpr Type Name = Value; // comment
private static readonly Regex ConstexprRegex = new(
@"^\s*constexpr\s+(\w+)\s+(\w+)\s*=\s*(.+?)\s*;\s*(?://\s*(.*))?$",
RegexOptions.Compiled);
// Matches: namespace Name {
// Also matches: namespace Name1::Name2 { (but we split on ::)
private static readonly Regex NamespaceRegex = new(
@"^\s*namespace\s+([\w:]+)\s*\{",
RegexOptions.Compiled);
// Matches closing brace with optional semicolon
private static readonly Regex CloseBraceRegex = new(
@"^\s*\}\s*;?\s*$",
RegexOptions.Compiled);
// Matches: typedef ... (to skip)
private static readonly Regex TypedefRegex = new(
@"^\s*typedef\s+",
RegexOptions.Compiled);
// Matches lines to skip inside structs
private static readonly Regex SkipLineRegex = new(
@"^\s*(inline|static\s+const|GWCA_API|virtual|explicit|friend|\[\[nodiscard\]\]|constexpr|template\s*<|//|#|public:|private:|protected:|$)",
RegexOptions.Compiled);
// Matches: union { (to skip union blocks)
private static readonly Regex UnionStartRegex = new(
@"^\s*union\s*\{",
RegexOptions.Compiled);
// Matches: template<...> struct/class (to skip)
private static readonly Regex TemplateStructRegex = new(
@"^\s*template\s*<",
RegexOptions.Compiled);
// Matches inline function definitions (static, inline, GWCA_API, etc.) that have a body
private static readonly Regex FunctionWithBodyRegex = new(
@"^\s*(?:static|inline|GWCA_API|virtual|explicit|constexpr).*\([^)]*\)\s*(?:const)?\s*\{",
RegexOptions.Compiled);
// Matches function declarations with body on next line (no opening brace)
private static readonly Regex FunctionNoBraceRegex = new(
@"^\s*(?:static|inline|GWCA_API|virtual|explicit|constexpr).*\([^)]*\)\s*(?:const)?\s*$",
RegexOptions.Compiled);
// Matches out-of-line method definitions: Type* Class::Method() { or Type Class::Method() const {
private static readonly Regex OutOfLineMethodRegex = new(
@"^\s*(?:const\s+)?[\w:*&<>]+(?:\s+[\w:*&<>]+)*\s+\w+::\w+\s*\([^)]*\)\s*(?:const)?\s*\{",
RegexOptions.Compiled);
// Matches standalone opening brace
private static readonly Regex OpenBraceOnlyRegex = new(
@"^\s*\{\s*$",
RegexOptions.Compiled);
public static List<string> DebugLines { get; } = [];
public static void Parse(string headerText, ConstantsNode root)
{
var lines = headerText.Split('\n');
var namespaceStack = new Stack<ConstantsNode>();
namespaceStack.Push(root);
// Parsing state
bool inEnum = false;
CppEnumDef? currentEnum = null;
bool inStruct = false;
CppStructDef? currentStruct = null;
int structBraceDepth = 0;
bool inSkipBlock = false;
int skipBlockBraceCount = 0;
bool inMultiLineConstexpr = false;
int multiLineBraceCount = 0;
int freeBraceDepth = 0; // Track braces from functions with body on separate line
// Track struct sizes from static_assert
var structSizes = new Dictionary<string, int>(StringComparer.Ordinal);
// First pass: collect sizeof assertions
foreach (var line in lines)
{
var sizeMatch = SizeofAssertRegex.Match(line);
if (sizeMatch.Success)
{
var name = sizeMatch.Groups[1].Value;
var sizeStr = sizeMatch.Groups[2].Value;
int size = sizeStr.StartsWith("0x", StringComparison.OrdinalIgnoreCase)
? Convert.ToInt32(sizeStr, 16)
: int.Parse(sizeStr);
structSizes[name] = size;
}
}
for (int i = 0; i < lines.Length; i++)
{
var line = lines[i].TrimEnd('\r');
// Skip preprocessor directives
if (line.TrimStart().StartsWith("#"))
continue;
var trimmed = line.TrimStart();
if (trimmed.Length == 0)
continue;
if (trimmed.StartsWith("//") && !inEnum && !inStruct)
continue;
// Handle multi-line constexpr (like constexpr std::array)
if (inMultiLineConstexpr)
{
multiLineBraceCount += CountChar(line, '{') - CountChar(line, '}');
if (multiLineBraceCount <= 0 || line.TrimEnd().EndsWith(";"))
{
inMultiLineConstexpr = false;
}
continue;
}
// Handle skip blocks (unions, template structs, etc.)
if (inSkipBlock)
{
skipBlockBraceCount += CountChar(line, '{') - CountChar(line, '}');
if (skipBlockBraceCount <= 0)
{
inSkipBlock = false;
}
continue;
}
// ── Inside an enum body (multi-line) ───────────────────
if (inEnum && currentEnum is not null)
{
if (trimmed.StartsWith("//"))
continue;
// Strip comments before checking for closing brace - comments can contain braces like "{ wchar_t* title }"
var trimmedWithoutComment = trimmed;
var commentIdx = trimmed.IndexOf("//");
if (commentIdx >= 0)
trimmedWithoutComment = trimmed.Substring(0, commentIdx).TrimEnd();
var closeBraceIdx = trimmedWithoutComment.IndexOf('}');
if (closeBraceIdx >= 0)
{
if (closeBraceIdx > 0)
{
ParseEnumMembersFromLine(trimmed.Substring(0, closeBraceIdx), currentEnum);
}
inEnum = false;
var node = namespaceStack.Peek();
node.Enums.Add(currentEnum);
currentEnum = null;
continue;
}
ParseEnumMembersFromLine(trimmed, currentEnum);
continue;
}
// ── Inside a struct body ───────────────────────────────
if (inStruct && currentStruct is not null)
{
// Strip comments before counting braces - comments can contain braces
var lineWithoutComments = StripComments(line);
int openBraces = CountChar(lineWithoutComments, '{');
int closeBraces = CountChar(lineWithoutComments, '}');
int prevDepth = structBraceDepth;
structBraceDepth += openBraces - closeBraces;
// Debug: Track brace changes for AgentLiving
if (currentStruct.Name == "AgentLiving" && (openBraces > 0 || closeBraces > 0))
{
DebugLines.Add($"[BRACE] line {i+1}: {currentStruct.Name} depth {prevDepth}->{structBraceDepth} (open={openBraces}, close={closeBraces})");
}
// If we hit the closing brace of the struct
if (structBraceDepth <= 0)
{
// Apply known size if available
if (structSizes.TryGetValue(currentStruct.Name, out var size))
currentStruct.Size = size;
var node = namespaceStack.Peek();
// Debug: capture namespace path
var pathParts = new List<string>();
foreach (var n in namespaceStack)
pathParts.Add(n.Name);
pathParts.Reverse();
currentStruct.DebugNamespacePath = string.Join(".", pathParts);
DebugLines.Add($"[STRUCT-END] line {i+1}: {currentStruct.Name} with {currentStruct.Fields.Count} fields");
node.Structs.Add(currentStruct);
currentStruct = null;
inStruct = false;
continue;
}
// Skip nested named structs (e.g., "struct Foo {") but NOT anonymous unions/structs
// We want to parse fields inside unions because they map to overlapping [FieldOffset] in C#
// Only skip if we're inside a named nested struct (not an anonymous union/struct block)
if (structBraceDepth > 1)
{
// Still try to parse fields with explicit offsets inside unions
// These are perfectly valid - they become overlapping fields in C# explicit layout
var fieldOffsetMatchNested = FieldWithOffsetRegex.Match(trimmed);
if (fieldOffsetMatchNested.Success)
{
var offset = Convert.ToInt32(fieldOffsetMatchNested.Groups[1].Value, 16);
var typeAndName = fieldOffsetMatchNested.Groups[2].Value;
var comment = fieldOffsetMatchNested.Groups[3].Success ? fieldOffsetMatchNested.Groups[3].Value.Trim() : null;
var field = ParseFieldTypeAndName(typeAndName, offset, comment);
if (field is not null)
currentStruct.Fields.Add(field);
}
continue;
}
// Skip comment-only lines
if (trimmed.StartsWith("//"))
continue;
// Skip method/static/inline lines
if (SkipLineRegex.IsMatch(trimmed))
continue;
// Skip lines with function bodies or method declarations
// Strip trailing // comment before checking - comments can contain parentheses like "(runes,pcons,etc)"
var trimmedWithoutTrailingComment = trimmed;
var trailingCommentIdx = trimmed.IndexOf("//");
if (trailingCommentIdx > 0)
trimmedWithoutTrailingComment = trimmed.Substring(0, trailingCommentIdx).TrimEnd();
if (trimmedWithoutTrailingComment.Contains("(") && (trimmedWithoutTrailingComment.Contains(")") || trimmedWithoutTrailingComment.Contains("{")))
continue;
// Skip bitfield declarations (e.g., DyeColor dye1 : 4;)
if (trimmed.Contains(" : ") && Regex.IsMatch(trimmed, @":\s*\d+\s*;"))
continue;
// Skip union starts
if (UnionStartRegex.IsMatch(trimmed))
continue;
// Try to parse field with offset
var fieldOffsetMatch = FieldWithOffsetRegex.Match(trimmed);
if (fieldOffsetMatch.Success)
{
var offset = Convert.ToInt32(fieldOffsetMatch.Groups[1].Value, 16);
var typeAndName = fieldOffsetMatch.Groups[2].Value;
var comment = fieldOffsetMatch.Groups[3].Success ? fieldOffsetMatch.Groups[3].Value.Trim() : null;
var field = ParseFieldTypeAndName(typeAndName, offset, comment);
if (field is not null)
currentStruct.Fields.Add(field);
continue;
}
// Try to parse field without offset
var fieldNoOffsetMatch = FieldNoOffsetRegex.Match(trimmed);
if (fieldNoOffsetMatch.Success)
{
var cppType = fieldNoOffsetMatch.Groups[1].Value.Trim();
var name = fieldNoOffsetMatch.Groups[2].Value;
var arraySize = fieldNoOffsetMatch.Groups[3].Success ? fieldNoOffsetMatch.Groups[3].Value.Trim() : null;
var comment = fieldNoOffsetMatch.Groups[4].Success ? fieldNoOffsetMatch.Groups[4].Value.Trim() : null;
var field = new CppStructField
{
Name = name,
CppType = cppType,
ArraySize = arraySize,
Comment = comment,
};
currentStruct.Fields.Add(field);
}
continue;
}
// ── Try to match namespace ─────────────────────────────
var nsMatch = NamespaceRegex.Match(trimmed);
if (nsMatch.Success)
{
var nsName = nsMatch.Groups[1].Value;
var parts = nsName.Split(new[] { "::" }, StringSplitOptions.RemoveEmptyEntries);
foreach (var part in parts)
{
var current = namespaceStack.Peek();
var child = current.GetOrCreateChild(part);
namespaceStack.Push(child);
}
continue;
}
// ── Closing brace (function body end or namespace end) ─
if (CloseBraceRegex.IsMatch(trimmed))
{
// If we have unclosed function bodies, close those first
if (freeBraceDepth > 0)
{
freeBraceDepth--;
continue;
}
if (namespaceStack.Count > 1)
{
// Debug: Track where namespace was popped
var poppedNode = namespaceStack.Pop();
poppedNode.DebugPopLine = i + 1;
}
continue;
}
// ── Skip static_assert ─────────────────────────────────
if (trimmed.StartsWith("static_assert"))
{
if (!trimmed.Contains(";"))
{
for (int j = i + 1; j < lines.Length; j++)
{
if (lines[j].Contains(";"))
{
i = j;
break;
}
}
}
continue;
}
// ── Handle typedef struct Name { (parse as regular struct) ─
var typedefStructMatch = Regex.Match(trimmed, @"^\s*typedef\s+struct\s+(\w+)\s*\{");
if (typedefStructMatch.Success)
{
var structName = typedefStructMatch.Groups[1].Value;
currentStruct = new CppStructDef
{
Name = structName,
};
inStruct = true;
structBraceDepth = 1;
continue;
}
// ── Handle typedef Array<T> AliasName; ───────────────────────
var typedefArrayMatch = Regex.Match(trimmed, @"^\s*typedef\s+(\w+)\s*<\s*([\w:\s*]+)\s*>\s+(\w+)\s*;");
if (typedefArrayMatch.Success)
{
var sourceType = typedefArrayMatch.Groups[1].Value;
var templateArg = typedefArrayMatch.Groups[2].Value.Trim();
var aliasName = typedefArrayMatch.Groups[3].Value;
namespaceStack.Peek().TypeAliases.Add(new CppTypeAlias
{
AliasName = aliasName,
SourceType = sourceType,
TemplateArg = templateArg
});
continue;
}
// ── Skip other typedef ─────────────────────────────────
if (TypedefRegex.IsMatch(trimmed))
continue;
// ── Skip template struct/class ─────────────────────────
if (TemplateStructRegex.IsMatch(trimmed))
{
int braces = CountChar(trimmed, '{') - CountChar(trimmed, '}');
if (braces > 0)
{
inSkipBlock = true;
skipBlockBraceCount = braces;
}
continue;
}
// ── Skip inline function bodies ────────────────────────
if (FunctionWithBodyRegex.IsMatch(trimmed))
{
int braces = CountChar(trimmed, '{') - CountChar(trimmed, '}');
if (braces > 0)
{
inSkipBlock = true;
skipBlockBraceCount = braces;
}
continue;
}
// ── Skip out-of-line method definitions (Type* Class::Method() {) ─
if (OutOfLineMethodRegex.IsMatch(trimmed))
{
int braces = CountChar(trimmed, '{') - CountChar(trimmed, '}');
DebugLines.Add($"[OUT-OF-LINE] line {i+1}: {trimmed.Substring(0, Math.Min(50, trimmed.Length))}... braces={braces}");
if (braces > 0)
{
inSkipBlock = true;
skipBlockBraceCount = braces;
}
continue;
}
else if (trimmed.Contains("::") && trimmed.Contains("(") && trimmed.Contains("{"))
{
DebugLines.Add($"[UNMATCHED-METHOD] line {i+1}: {trimmed.Substring(0, Math.Min(60, trimmed.Length))}");
}
// ── Track function declarations with body on separate line ─
if (FunctionNoBraceRegex.IsMatch(trimmed))
{
// The next meaningful line should be { which starts the body
// We'll handle this by looking for standalone { and tracking brace depth
continue;
}
// ── Track standalone opening braces (function bodies with { on separate line)
if (OpenBraceOnlyRegex.IsMatch(trimmed))
{
freeBraceDepth++;
continue;
}
// ── Skip constexpr std::array etc. ─────────────────────
if (trimmed.StartsWith("constexpr std::"))
{
int braces = CountChar(trimmed, '{') - CountChar(trimmed, '}');
if (braces > 0)
{
inMultiLineConstexpr = true;
multiLineBraceCount = braces;
}
continue;
}
// ── Try to match struct start ──────────────────────────
var structMatch = StructStartRegex.Match(trimmed);
if (structMatch.Success)
{
var structName = structMatch.Groups[1].Value;
var baseType = structMatch.Groups[2].Success ? structMatch.Groups[2].Value : null;
DebugLines.Add($"[STRUCT-START] line {i+1}: {structName} (inSkip={inSkipBlock}, freeBrace={freeBraceDepth})");
// Skip some special cases
if (structName.StartsWith("__") || structName == "Packet")
{
int braces = CountChar(trimmed, '{') - CountChar(trimmed, '}');
if (braces > 0)
{
inSkipBlock = true;
skipBlockBraceCount = braces;
}
continue;
}
currentStruct = new CppStructDef
{
Name = structName,
BaseType = baseType,
};
inStruct = true;
structBraceDepth = 1;
continue;
}
// ── Struct without opening brace on same line ──────────
var structNobraceMatch = StructNobraceRegex.Match(trimmed);
if (structNobraceMatch.Success)
{
var structName = structNobraceMatch.Groups[1].Value;
// Skip forward declarations (just struct Name;)
if (trimmed.EndsWith(";"))
continue;
// Skip special cases
if (structName.StartsWith("__") || structName == "Packet")
continue;
// Look ahead for opening brace
for (int j = i + 1; j < lines.Length; j++)
{
var nextTrimmed = lines[j].TrimStart().TrimEnd('\r');
if (nextTrimmed.Length == 0 || nextTrimmed.StartsWith("//"))
continue;
if (nextTrimmed.Contains("{"))
{
var baseType = structNobraceMatch.Groups[2].Success ? structNobraceMatch.Groups[2].Value : null;
currentStruct = new CppStructDef
{
Name = structName,
BaseType = baseType,
};
inStruct = true;
structBraceDepth = 1;
i = j;
break;
}
break; // unexpected content
}
continue;
}
// ── Try to match enum start (with opening brace on same line) ──
var enumMatch = EnumStartRegex.Match(trimmed);
if (enumMatch.Success)
{
var enumDef = new CppEnumDef
{
Name = enumMatch.Groups[1].Success && enumMatch.Groups[1].Value.Length > 0
? enumMatch.Groups[1].Value : null,
UnderlyingType = enumMatch.Groups[2].Success && enumMatch.Groups[2].Value.Length > 0
? enumMatch.Groups[2].Value : null,
};
var afterBrace = trimmed.Substring(enumMatch.Length);
var closeBraceIdx = afterBrace.IndexOf('}');
if (closeBraceIdx >= 0)
{
var body = afterBrace.Substring(0, closeBraceIdx);
ParseEnumMembersFromLine(body, enumDef);
var node = namespaceStack.Peek();
node.Enums.Add(enumDef);
}
else
{
var rest = afterBrace.Trim();
if (rest.Length > 0)
{
ParseEnumMembersFromLine(rest, enumDef);
}
currentEnum = enumDef;
inEnum = true;
}
continue;
}
// ── Enum without opening brace on same line ────────────
var enumNobraceMatch = EnumNobraceRegex.Match(trimmed);
if (enumNobraceMatch.Success)
{
for (int j = i + 1; j < lines.Length; j++)
{
var nextTrimmed = lines[j].TrimStart().TrimEnd('\r');
if (nextTrimmed.Length == 0 || nextTrimmed.StartsWith("//"))
continue;
if (nextTrimmed.Contains("{"))
{
var enumDef = new CppEnumDef
{
Name = enumNobraceMatch.Groups[1].Success && enumNobraceMatch.Groups[1].Value.Length > 0
? enumNobraceMatch.Groups[1].Value : null,
UnderlyingType = enumNobraceMatch.Groups[2].Success && enumNobraceMatch.Groups[2].Value.Length > 0
? enumNobraceMatch.Groups[2].Value : null,
};
var braceIdx = nextTrimmed.IndexOf('{');
var afterBrace = nextTrimmed.Substring(braceIdx + 1);
var closeBraceIdx = afterBrace.IndexOf('}');
if (closeBraceIdx >= 0)
{
var body = afterBrace.Substring(0, closeBraceIdx);
ParseEnumMembersFromLine(body, enumDef);
var node = namespaceStack.Peek();
node.Enums.Add(enumDef);
}
else
{
var rest = afterBrace.Trim();
if (rest.Length > 0)
{
ParseEnumMembersFromLine(rest, enumDef);
}
currentEnum = enumDef;
inEnum = true;
}
i = j;
break;
}
break; // unexpected content
}
continue;
}
// ── Try to match constexpr field ───────────────────────
var constMatch = ConstexprRegex.Match(trimmed);
if (constMatch.Success)
{
var cppType = constMatch.Groups[1].Value;
var name = constMatch.Groups[2].Value;
var value = constMatch.Groups[3].Value.Trim();
var comment = constMatch.Groups[4].Success ? constMatch.Groups[4].Value.Trim() : null;
// Skip complex expressions with :: references or casts
if (value.Contains("::") || value.Contains("(") || value.Contains("static_cast"))
continue;
var field = new CppConstField
{
Name = name,
CppType = cppType,
Value = NormalizeCppValue(value),
Comment = comment,
};
var node = namespaceStack.Peek();
node.Constants.Add(field);
continue;
}
}
}
/// <summary>
/// Parses a combined "Type Name" or "Type Name[Size]" string into a field.
/// </summary>
private static CppStructField? ParseFieldTypeAndName(string typeAndName, int offset, string? comment)
{
typeAndName = typeAndName.Trim();
// Handle array notation: uint32_t name[7]
string? arraySize = null;
var bracketIdx = typeAndName.IndexOf('[');
if (bracketIdx >= 0)
{
var closeBracket = typeAndName.IndexOf(']', bracketIdx);
if (closeBracket > bracketIdx)
{
arraySize = typeAndName.Substring(bracketIdx + 1, closeBracket - bracketIdx - 1).Trim();
typeAndName = typeAndName.Substring(0, bracketIdx).Trim();
}
}
// Split type and name - name is the last token
var tokens = typeAndName.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
if (tokens.Length < 2)
return null;
var name = tokens[tokens.Length - 1].TrimStart('*');
var cppType = string.Join(" ", tokens.Take(tokens.Length - 1));
// Handle pointer attached to name: Item *weapon -> Item*, weapon
if (tokens[tokens.Length - 1].StartsWith("*"))
{
cppType += "*";
}
// Handle pointer attached to type: Item* weapon
else if (cppType.EndsWith("*"))
{
// Already correct
}
// Handle pointer in middle: Item * weapon
else if (tokens.Any(t => t == "*"))
{
cppType = cppType.Replace(" *", "*").Replace("* ", "*");
if (!cppType.EndsWith("*"))
cppType += "*";
}
return new CppStructField
{
Name = name,
CppType = cppType.Trim(),
Offset = offset,
ArraySize = arraySize,
Comment = comment,
};
}
/// <summary>
/// Parses comma-separated enum members from a single line of text.
/// </summary>
private static void ParseEnumMembersFromLine(string line, CppEnumDef enumDef)
{
string? lineComment = null;
var commentIdx = line.IndexOf("//");
if (commentIdx >= 0)
{
lineComment = line.Substring(commentIdx + 2).Trim();
line = line.Substring(0, commentIdx);
}
var parts = line.Split(',');
CppEnumMember? lastMember = null;
foreach (var part in parts)
{
var trimPart = part.Trim();
if (trimPart.Length == 0)
continue;
var eqIdx = trimPart.IndexOf('=');
string name;
string? value = null;
if (eqIdx >= 0)
{
name = trimPart.Substring(0, eqIdx).Trim();
value = trimPart.Substring(eqIdx + 1).Trim();
}
else
{
name = trimPart;
}
if (name.Length > 0 && (char.IsLetter(name[0]) || name[0] == '_'))
{
var member = new CppEnumMember
{
Name = name,
Value = value,
};
enumDef.Members.Add(member);
lastMember = member;
}
}
if (lastMember is not null && lineComment is not null)
{
lastMember.Comment = lineComment;
}
}
/// <summary>
/// Strips C++ comments from a line (both // and /* */ style).
/// </summary>
private static string StripComments(string line)
{
// Remove // style comments
int slashSlash = line.IndexOf("//");
if (slashSlash >= 0)
line = line.Substring(0, slashSlash);
// Remove /* */ style comments (simple - doesn't handle nested)
int blockStart = line.IndexOf("/*");
while (blockStart >= 0)
{
int blockEnd = line.IndexOf("*/", blockStart + 2);
if (blockEnd >= 0)
line = line.Substring(0, blockStart) + line.Substring(blockEnd + 2);
else
line = line.Substring(0, blockStart);
blockStart = line.IndexOf("/*");
}
return line;
}
private static int CountChar(string s, char c)
{
int count = 0;
foreach (var ch in s)
if (ch == c) count++;
return count;
}
/// <summary>
/// Normalizes a C++ value literal to a C# compatible one.
/// </summary>
private static string NormalizeCppValue(string value)
{
value = value.Trim().TrimEnd(',');
// Remove 'u' or 'U' suffix from integer literals
if (value.EndsWith("u", StringComparison.OrdinalIgnoreCase) &&
!value.EndsWith("0xu", StringComparison.OrdinalIgnoreCase))
{
var candidate = value.Substring(0, value.Length - 1);
if (IsIntegerLiteral(candidate))
return candidate;
}
// Normalize C++ float suffix to valid C# float literal
if (value.EndsWith("f", StringComparison.OrdinalIgnoreCase))
{
value = value.Substring(0, value.Length - 1);
if (value.EndsWith("."))
value += "0";
return value + "f";
}
// If it's a float-looking literal without suffix, add f
if (value.Contains(".") && !value.Contains("\""))
return value + "f";
return value;
}
private static bool IsIntegerLiteral(string s)
{
s = s.Trim();
if (s.Length == 0) return false;
if (s.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
return s.Length > 2 && s.Substring(2).All(c => "0123456789abcdefABCDEF".Contains(c));
var start = s[0] == '-' ? 1 : 0;
if (start >= s.Length) return false;
return s.Substring(start).All(char.IsDigit);
}
}