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 DebugLines { get; } = []; public static void Parse(string headerText, ConstantsNode root) { var lines = headerText.Split('\n'); var namespaceStack = new Stack(); 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(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(); 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 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; } } } /// /// Parses a combined "Type Name" or "Type Name[Size]" string into a field. /// 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, }; } /// /// Parses comma-separated enum members from a single line of text. /// 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; } } /// /// Strips C++ comments from a line (both // and /* */ style). /// 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; } /// /// Normalizes a C++ value literal to a C# compatible one. /// 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); } }