Backlog/lib/sdl3/parser/naming.zig

290 lines
9.9 KiB
Zig

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);
}