const std = @import("std"); const Allocator = std.mem.Allocator; /// Remove SDL_ prefix from a name pub fn stripSDLPrefix(name: []const u8) []const u8 { if (std.mem.startsWith(u8, name, "SDL_")) { return name[4..]; } return name; } /// Convert SDL type name to Zig type name /// SDL_GPUDevice -> GPUDevice pub fn typeNameToZig(c_name: []const u8) []const u8 { return stripSDLPrefix(c_name); } /// Convert SDL function name to Zig function name /// SDL_CreateGPUDevice -> createGPUDevice pub fn functionNameToZig(c_name: []const u8, allocator: Allocator) ![]const u8 { const without_prefix = stripSDLPrefix(c_name); if (without_prefix.len == 0) return try allocator.dupe(u8, c_name); var result = try allocator.dupe(u8, without_prefix); // Lowercase leading acronyms (e.g., "GPUSupports" -> "gpuSupports") // An acronym is multiple consecutive uppercase letters var i: usize = 0; while (i < result.len and std.ascii.isUpper(result[i])) : (i += 1) { // If we have at least 2 uppercase letters and the next char is lowercase, // we've found the end of the acronym (e.g., "GPUs" -> "gpu" + "Supports") if (i > 0 and i + 1 < result.len and std.ascii.isLower(result[i + 1])) { // Don't lowercase this last uppercase letter - it starts the next word break; } result[i] = std.ascii.toLower(result[i]); } return result; } /// Detect common prefix in a list of names /// For SDL3, this should only strip the SDL_GPU_ or SDL_ prefix, /// NOT the type name portion. This allows the type name to be preserved /// in the enum values, preventing invalid identifiers that start with numbers. pub fn detectCommonPrefix(names: []const []const u8, allocator: Allocator) ![]const u8 { if (names.len == 0) return try allocator.dupe(u8, ""); const first = names[0]; // For SDL3, we want to find the "SDL_GPU_" or "SDL_" prefix // but NOT include the type name part if (std.mem.startsWith(u8, first, "SDL_GPU_")) { return try allocator.dupe(u8, "SDL_GPU_"); } else if (std.mem.startsWith(u8, first, "SDL_")) { return try allocator.dupe(u8, "SDL_"); } return try allocator.dupe(u8, ""); } /// Convert enum value name to Zig using the "first underscore after prefix" rule /// SDL_GPU_PRIMITIVETYPE_TRIANGLELIST -> primitivetypeTrianglelist /// SDL_GPU_TEXTURETYPE_2D_ARRAY -> texturetype2dArray /// SDL_GPU_SAMPLECOUNT_1 -> samplecount1 pub fn enumValueToZig(c_name: []const u8, prefix: []const u8, allocator: Allocator) ![]const u8 { // Remove SDL_GPU_ or SDL_ prefix var name = c_name; if (std.mem.startsWith(u8, name, prefix)) { name = name[prefix.len..]; } // Find FIRST underscore: splits type name from value // e.g., "PRIMITIVETYPE_TRIANGLELIST" -> "PRIMITIVETYPE" + "TRIANGLELIST" // e.g., "TEXTURETYPE_2D_ARRAY" -> "TEXTURETYPE" + "2D_ARRAY" const first_underscore = std.mem.indexOfScalar(u8, name, '_'); if (first_underscore) |pos| { const type_part = name[0..pos]; // "PRIMITIVETYPE" or "TEXTURETYPE" const value_part = name[pos + 1 ..]; // "TRIANGLELIST" or "2D_ARRAY" // Build result var result = try std.ArrayList(u8).initCapacity(allocator, name.len); errdefer result.deinit(allocator); // Type part: all lowercase for (type_part) |c| { try result.append(allocator, std.ascii.toLower(c)); } // Value part: convert to camelCase (first letter uppercase, handle underscores) const value_camel = try screaminToTitleCamel(value_part, allocator); defer allocator.free(value_camel); try result.appendSlice(allocator, value_camel); return try result.toOwnedSlice(allocator); } else { // No underscore found - just convert to lowercase // This handles single-word enum values return try screaminToLowerCamel(name, allocator); } } /// Convert flag name to Zig /// SDL_GPU_TEXTUREUSAGE_SAMPLER -> textureusageSampler pub fn flagNameToZig(c_name: []const u8, prefix: []const u8, allocator: Allocator) ![]const u8 { return enumValueToZig(c_name, prefix, allocator); } /// Convert SCREAMING_SNAKE_CASE to lowerCamelCase fn screaminToLowerCamel(s: []const u8, allocator: Allocator) ![]const u8 { if (s.len == 0) return try allocator.dupe(u8, ""); var result = try std.ArrayList(u8).initCapacity(allocator, s.len); errdefer result.deinit(allocator); var capitalize_next = false; var is_first = true; for (s) |c| { if (c == '_') { capitalize_next = true; continue; } if (is_first) { try result.append(allocator, std.ascii.toLower(c)); is_first = false; } else if (capitalize_next) { try result.append(allocator, std.ascii.toUpper(c)); capitalize_next = false; } else { try result.append(allocator, std.ascii.toLower(c)); } } return try result.toOwnedSlice(allocator); } /// Convert SCREAMING_SNAKE_CASE to TitleCamelCase (first letter uppercase) fn screaminToTitleCamel(s: []const u8, allocator: Allocator) ![]const u8 { if (s.len == 0) return try allocator.dupe(u8, ""); var result = try std.ArrayList(u8).initCapacity(allocator, s.len); errdefer result.deinit(allocator); var capitalize_next = true; // Start with capitalize for TitleCase for (s) |c| { if (c == '_') { capitalize_next = true; continue; } if (capitalize_next) { try result.append(allocator, std.ascii.toUpper(c)); capitalize_next = false; } else { try result.append(allocator, std.ascii.toLower(c)); } } return try result.toOwnedSlice(allocator); } test "strip SDL prefix" { try std.testing.expectEqualStrings("GPUDevice", stripSDLPrefix("SDL_GPUDevice")); try std.testing.expectEqualStrings("Foo", stripSDLPrefix("SDL_Foo")); try std.testing.expectEqualStrings("Bar", stripSDLPrefix("Bar")); } test "type name to Zig" { try std.testing.expectEqualStrings("GPUDevice", typeNameToZig("SDL_GPUDevice")); try std.testing.expectEqualStrings("GPUPrimitiveType", typeNameToZig("SDL_GPUPrimitiveType")); } test "function name to Zig" { const name1 = try functionNameToZig("SDL_CreateGPUDevice", std.testing.allocator); defer std.testing.allocator.free(name1); try std.testing.expectEqualStrings("createGPUDevice", name1); const name2 = try functionNameToZig("SDL_DestroyGPUDevice", std.testing.allocator); defer std.testing.allocator.free(name2); try std.testing.expectEqualStrings("destroyGPUDevice", name2); } test "detect common prefix - should only strip SDL prefix" { const names = [_][]const u8{ "SDL_GPU_PRIMITIVETYPE_TRIANGLELIST", "SDL_GPU_PRIMITIVETYPE_TRIANGLESTRIP", "SDL_GPU_PRIMITIVETYPE_LINELIST", }; const prefix = try detectCommonPrefix(&names, std.testing.allocator); defer std.testing.allocator.free(prefix); // Should only strip SDL_GPU_, not the type name try std.testing.expectEqualStrings("SDL_GPU_", prefix); } test "detect common prefix - SDL without GPU" { const names = [_][]const u8{ "SDL_LOADOP_LOAD", "SDL_LOADOP_CLEAR", }; const prefix = try detectCommonPrefix(&names, std.testing.allocator); defer std.testing.allocator.free(prefix); try std.testing.expectEqualStrings("SDL_", prefix); } test "enum value to Zig - basic case" { const result = try enumValueToZig( "SDL_GPU_PRIMITIVETYPE_TRIANGLELIST", "SDL_GPU_", std.testing.allocator, ); defer std.testing.allocator.free(result); try std.testing.expectEqualStrings("primitivetypeTrianglelist", result); } test "enum value to Zig - numeric value" { const result = try enumValueToZig( "SDL_GPU_SAMPLECOUNT_1", "SDL_GPU_", std.testing.allocator, ); defer std.testing.allocator.free(result); try std.testing.expectEqualStrings("samplecount1", result); } test "enum value to Zig - number in middle" { const result = try enumValueToZig( "SDL_GPU_TEXTURETYPE_2D_ARRAY", "SDL_GPU_", std.testing.allocator, ); defer std.testing.allocator.free(result); try std.testing.expectEqualStrings("texturetype2dArray", result); } test "enum value to Zig - simple number" { const result = try enumValueToZig( "SDL_GPU_TEXTURETYPE_2D", "SDL_GPU_", std.testing.allocator, ); defer std.testing.allocator.free(result); try std.testing.expectEqualStrings("texturetype2d", result); } test "enum value to Zig - 16bit case" { const result = try enumValueToZig( "SDL_GPU_INDEXELEMENTSIZE_16BIT", "SDL_GPU_", std.testing.allocator, ); defer std.testing.allocator.free(result); try std.testing.expectEqualStrings("indexelementsize16bit", result); } test "flag name to Zig - basic case" { const result = try flagNameToZig( "SDL_GPU_TEXTUREUSAGE_SAMPLER", "SDL_GPU_", std.testing.allocator, ); defer std.testing.allocator.free(result); try std.testing.expectEqualStrings("textureusageSampler", result); } test "flag name to Zig - complex name" { const result = try flagNameToZig( "SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_SIMULTANEOUS_READ_WRITE", "SDL_GPU_", std.testing.allocator, ); defer std.testing.allocator.free(result); try std.testing.expectEqualStrings("textureusageComputeStorageSimultaneousReadWrite", result); } test "screaming to lower camel" { const result1 = try screaminToLowerCamel("TRIANGLE_LIST", std.testing.allocator); defer std.testing.allocator.free(result1); try std.testing.expectEqualStrings("triangleList", result1); const result2 = try screaminToLowerCamel("SAMPLER", std.testing.allocator); defer std.testing.allocator.free(result2); try std.testing.expectEqualStrings("sampler", result2); }