this enum detection code is getting wild. but it captures much more edge cases

This commit is contained in:
peterino2 2026-01-26 23:27:53 -08:00
parent a25a09e8fb
commit 094ac7b0b9
13 changed files with 524 additions and 80 deletions

View File

@ -168,9 +168,11 @@ pub inline fn getWindows(count: *c_int) [*c]?*Window {
After generating Zig code:
1. Output written to `tmp/` directory
2. `zig ast-check` runs on the temporary file
3. If passes: `zig fmt` runs, then copied to final destination
3. If passes: `zig fmt` runs, then copied to final destination (`api/`)
4. If fails: debug output written to `debug/` with `_fmterror.zig` suffix
**Note:** If a file exists in `api/`, it has already been validated. No need to run `zig ast-check` manually on final output files.
**Testing all outputs:**
Use `zig build generate` to regenerate all SDL3 headers and verify no regressions.

View File

@ -34,6 +34,8 @@ pub const MouseButtonFlags = packed struct(u32) {
rsvd: bool = false,
pub const None = MouseButtonFlags{};
pub const ButtonRight: MouseButtonFlags = @bitCast(@as(u32, 3));
pub const ButtonX2: MouseButtonFlags = @bitCast(@as(u32, 5));
};
pub const Scancode = enum(c_int) {

View File

@ -75,6 +75,8 @@ pub const MouseButtonFlags = packed struct(u32) {
rsvd: bool = false,
pub const None = MouseButtonFlags{};
pub const ButtonRight: MouseButtonFlags = @bitCast(@as(u32, 3));
pub const ButtonX2: MouseButtonFlags = @bitCast(@as(u32, 5));
};
pub const MouseMotionTransformCallback = c.SDL_MouseMotionTransformCallback;

View File

@ -676,6 +676,8 @@ pub const MouseButtonFlags = packed struct(u32) {
rsvd: bool = false,
pub const None = MouseButtonFlags{};
pub const ButtonRight: MouseButtonFlags = @bitCast(@as(u32, 3));
pub const ButtonX2: MouseButtonFlags = @bitCast(@as(u32, 5));
};
pub const PenInputFlags = packed struct(u32) {

View File

@ -2337,10 +2337,6 @@
"name": "SDL_GPUShaderFormat",
"underlying_type": "Uint32",
"values": [
{
"name": "SDL_GPU_SHADERFORMAT_INVALID",
"value": "0"
},
{
"name": "SDL_GPU_SHADERFORMAT_PRIVATE",
"value": "(1u << 0)",

View File

@ -48,40 +48,40 @@
"values": [
{
"name": "SDL_INIT_AUDIO",
"value": "0x00000010u",
"value": "(1u << 4)",
"comment": "`SDL_INIT_AUDIO` implies `SDL_INIT_EVENTS`"
},
{
"name": "SDL_INIT_VIDEO",
"value": "0x00000020u",
"value": "(1u << 5)",
"comment": "`SDL_INIT_VIDEO` implies `SDL_INIT_EVENTS`, should be initialized on the main thread"
},
{
"name": "SDL_INIT_JOYSTICK",
"value": "0x00000200u",
"value": "(1u << 9)",
"comment": "`SDL_INIT_JOYSTICK` implies `SDL_INIT_EVENTS`"
},
{
"name": "SDL_INIT_HAPTIC",
"value": "0x00001000u"
"value": "(1u << 12)"
},
{
"name": "SDL_INIT_GAMEPAD",
"value": "0x00002000u",
"value": "(1u << 13)",
"comment": "`SDL_INIT_GAMEPAD` implies `SDL_INIT_JOYSTICK`"
},
{
"name": "SDL_INIT_EVENTS",
"value": "0x00004000u"
"value": "(1u << 14)"
},
{
"name": "SDL_INIT_SENSOR",
"value": "0x00008000u",
"value": "(1u << 15)",
"comment": "`SDL_INIT_SENSOR` implies `SDL_INIT_EVENTS`"
},
{
"name": "SDL_INIT_CAMERA",
"value": "0x00010000u",
"value": "(1u << 16)",
"comment": "`SDL_INIT_CAMERA` implies `SDL_INIT_EVENTS`"
}
]

View File

@ -122,27 +122,27 @@
"values": [
{
"name": "SDL_MESSAGEBOX_ERROR",
"value": "0x00000010u",
"value": "(1u << 4)",
"comment": "error dialog"
},
{
"name": "SDL_MESSAGEBOX_WARNING",
"value": "0x00000020u",
"value": "(1u << 5)",
"comment": "warning dialog"
},
{
"name": "SDL_MESSAGEBOX_INFORMATION",
"value": "0x00000040u",
"value": "(1u << 6)",
"comment": "informational dialog"
},
{
"name": "SDL_MESSAGEBOX_BUTTONS_LEFT_TO_RIGHT",
"value": "0x00000080u",
"value": "(1u << 7)",
"comment": "buttons placed left to right"
},
{
"name": "SDL_MESSAGEBOX_BUTTONS_RIGHT_TO_LEFT",
"value": "0x00000100u",
"value": "(1u << 8)",
"comment": "buttons placed right to left"
}
]
@ -153,12 +153,12 @@
"values": [
{
"name": "SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT",
"value": "0x00000001u",
"value": "(1u << 0)",
"comment": "Marks the default button when return is hit"
},
{
"name": "SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT",
"value": "0x00000002u",
"value": "(1u << 1)",
"comment": "Marks the default button when escape is hit"
}
]

View File

@ -145,23 +145,15 @@
"values": [
{
"name": "SDL_BUTTON_LEFT",
"value": "1"
"value": "(1u << 0)"
},
{
"name": "SDL_BUTTON_MIDDLE",
"value": "2"
},
{
"name": "SDL_BUTTON_RIGHT",
"value": "3"
"value": "(1u << 1)"
},
{
"name": "SDL_BUTTON_X1",
"value": "4"
},
{
"name": "SDL_BUTTON_X2",
"value": "5"
"value": "(1u << 2)"
}
]
}

View File

@ -57,22 +57,22 @@
"values": [
{
"name": "SDL_SURFACE_PREALLOCATED",
"value": "0x00000001u",
"value": "(1u << 0)",
"comment": "Surface uses preallocated pixel memory"
},
{
"name": "SDL_SURFACE_LOCK_NEEDED",
"value": "0x00000002u",
"value": "(1u << 1)",
"comment": "Surface needs to be locked to access pixels"
},
{
"name": "SDL_SURFACE_LOCKED",
"value": "0x00000004u",
"value": "(1u << 2)",
"comment": "Surface is currently locked"
},
{
"name": "SDL_SURFACE_SIMD_ALIGNED",
"value": "0x00000008u",
"value": "(1u << 3)",
"comment": "Surface uses pixel memory allocated with SDL_aligned_alloc()"
}
]

View File

@ -379,132 +379,132 @@
"values": [
{
"name": "SDL_WINDOW_FULLSCREEN",
"value": "SDL_UINT64_C(0x0000000000000001)",
"value": "(1u << 0)",
"comment": "window is in fullscreen mode"
},
{
"name": "SDL_WINDOW_OPENGL",
"value": "SDL_UINT64_C(0x0000000000000002)",
"value": "(1u << 1)",
"comment": "window usable with OpenGL context"
},
{
"name": "SDL_WINDOW_OCCLUDED",
"value": "SDL_UINT64_C(0x0000000000000004)",
"value": "(1u << 2)",
"comment": "window is occluded"
},
{
"name": "SDL_WINDOW_HIDDEN",
"value": "SDL_UINT64_C(0x0000000000000008)",
"value": "(1u << 3)",
"comment": "window is neither mapped onto the desktop nor shown in the taskbar/dock/window list; SDL_ShowWindow() is required for it to become visible"
},
{
"name": "SDL_WINDOW_BORDERLESS",
"value": "SDL_UINT64_C(0x0000000000000010)",
"value": "(1u << 4)",
"comment": "no window decoration"
},
{
"name": "SDL_WINDOW_RESIZABLE",
"value": "SDL_UINT64_C(0x0000000000000020)",
"value": "(1u << 5)",
"comment": "window can be resized"
},
{
"name": "SDL_WINDOW_MINIMIZED",
"value": "SDL_UINT64_C(0x0000000000000040)",
"value": "(1u << 6)",
"comment": "window is minimized"
},
{
"name": "SDL_WINDOW_MAXIMIZED",
"value": "SDL_UINT64_C(0x0000000000000080)",
"value": "(1u << 7)",
"comment": "window is maximized"
},
{
"name": "SDL_WINDOW_MOUSE_GRABBED",
"value": "SDL_UINT64_C(0x0000000000000100)",
"value": "(1u << 8)",
"comment": "window has grabbed mouse input"
},
{
"name": "SDL_WINDOW_INPUT_FOCUS",
"value": "SDL_UINT64_C(0x0000000000000200)",
"value": "(1u << 9)",
"comment": "window has input focus"
},
{
"name": "SDL_WINDOW_MOUSE_FOCUS",
"value": "SDL_UINT64_C(0x0000000000000400)",
"value": "(1u << 10)",
"comment": "window has mouse focus"
},
{
"name": "SDL_WINDOW_EXTERNAL",
"value": "SDL_UINT64_C(0x0000000000000800)",
"value": "(1u << 11)",
"comment": "window not created by SDL"
},
{
"name": "SDL_WINDOW_MODAL",
"value": "SDL_UINT64_C(0x0000000000001000)",
"value": "(1u << 12)",
"comment": "window is modal"
},
{
"name": "SDL_WINDOW_HIGH_PIXEL_DENSITY",
"value": "SDL_UINT64_C(0x0000000000002000)",
"value": "(1u << 13)",
"comment": "window uses high pixel density back buffer if possible"
},
{
"name": "SDL_WINDOW_MOUSE_CAPTURE",
"value": "SDL_UINT64_C(0x0000000000004000)",
"value": "(1u << 14)",
"comment": "window has mouse captured (unrelated to MOUSE_GRABBED)"
},
{
"name": "SDL_WINDOW_MOUSE_RELATIVE_MODE",
"value": "SDL_UINT64_C(0x0000000000008000)",
"value": "(1u << 15)",
"comment": "window has relative mode enabled"
},
{
"name": "SDL_WINDOW_ALWAYS_ON_TOP",
"value": "SDL_UINT64_C(0x0000000000010000)",
"value": "(1u << 16)",
"comment": "window should always be above others"
},
{
"name": "SDL_WINDOW_UTILITY",
"value": "SDL_UINT64_C(0x0000000000020000)",
"value": "(1u << 17)",
"comment": "window should be treated as a utility window, not showing in the task bar and window list"
},
{
"name": "SDL_WINDOW_TOOLTIP",
"value": "SDL_UINT64_C(0x0000000000040000)",
"value": "(1u << 18)",
"comment": "window should be treated as a tooltip and does not get mouse or keyboard focus, requires a parent window"
},
{
"name": "SDL_WINDOW_POPUP_MENU",
"value": "SDL_UINT64_C(0x0000000000080000)",
"value": "(1u << 19)",
"comment": "window should be treated as a popup menu, requires a parent window"
},
{
"name": "SDL_WINDOW_KEYBOARD_GRABBED",
"value": "SDL_UINT64_C(0x0000000000100000)",
"value": "(1u << 20)",
"comment": "window has grabbed keyboard input"
},
{
"name": "SDL_WINDOW_FILL_DOCUMENT",
"value": "SDL_UINT64_C(0x0000000000200000)",
"value": "(1u << 21)",
"comment": "window is in fill-document mode (Emscripten only), since SDL 3.4.0"
},
{
"name": "SDL_WINDOW_VULKAN",
"value": "SDL_UINT64_C(0x0000000010000000)",
"value": "(1u << 28)",
"comment": "window usable for Vulkan surface"
},
{
"name": "SDL_WINDOW_METAL",
"value": "SDL_UINT64_C(0x0000000020000000)",
"value": "(1u << 29)",
"comment": "window usable for Metal view"
},
{
"name": "SDL_WINDOW_TRANSPARENT",
"value": "SDL_UINT64_C(0x0000000040000000)",
"value": "(1u << 30)",
"comment": "window with transparent buffer"
},
{
"name": "SDL_WINDOW_NOT_FOCUSABLE",
"value": "SDL_UINT64_C(0x0000000080000000)",
"value": "(1u << 31)",
"comment": "window should not be focusable"
}
]

View File

@ -287,6 +287,7 @@ pub const CodeGen = struct {
.name = try self.allocator.dupe(u8, enum_decl.name),
.underlying_type = try self.allocator.dupe(u8, "Uint32"),
.flags = try flags_list.toOwnedSlice(self.allocator),
.constants = &[_]patterns.ConstValue{}, // Empty for enum-to-flag conversion
.doc_comment = if (enum_decl.doc_comment) |doc| try self.allocator.dupe(u8, doc) else null,
};
}
@ -576,6 +577,40 @@ pub const CodeGen = struct {
// Add None constant for zero value (C header parity)
try self.output.writer(self.allocator).print(" pub const None = {s}{{}};\n", .{zig_name});
// Add constant values for non-power-of-2 defines
if (flag_decl.constants.len > 0) {
for (flag_decl.constants) |constant| {
const zig_const_camel = try naming.flagNameToZig(constant.name, prefix, self.allocator);
defer self.allocator.free(zig_const_camel);
// Capitalize first letter for pub const (PascalCase)
const zig_const = try self.allocator.alloc(u8, zig_const_camel.len);
errdefer self.allocator.free(zig_const);
@memcpy(zig_const, zig_const_camel);
if (zig_const.len > 0) {
zig_const[0] = std.ascii.toUpper(zig_const[0]);
}
defer self.allocator.free(zig_const);
if (constant.comment) |comment| {
try self.output.writer(self.allocator).print(" pub const {s}: {s} = @bitCast(@as({s}, {s})); // {s}\n", .{
zig_const,
zig_name,
underlying_type,
constant.value,
comment,
});
} else {
try self.output.writer(self.allocator).print(" pub const {s}: {s} = @bitCast(@as({s}, {s}));\n", .{
zig_const,
zig_name,
underlying_type,
constant.value,
});
}
}
}
try self.output.appendSlice(self.allocator, "};\n\n");
}

View File

@ -287,6 +287,7 @@ fn cloneDeclaration(allocator: Allocator, decl: Declaration) !Declaration {
.name = try allocator.dupe(u8, f.name),
.underlying_type = try allocator.dupe(u8, f.underlying_type),
.flags = try cloneFlagValues(allocator, f.flags),
.constants = try cloneConstValues(allocator, f.constants),
.doc_comment = if (f.doc_comment) |doc| try allocator.dupe(u8, doc) else null,
},
},
@ -339,6 +340,18 @@ fn cloneFlagValues(allocator: Allocator, flags: []const patterns.FlagValue) ![]p
return new_flags;
}
fn cloneConstValues(allocator: Allocator, constants: []const patterns.ConstValue) ![]patterns.ConstValue {
const new_constants = try allocator.alloc(patterns.ConstValue, constants.len);
for (constants, 0..) |constant, i| {
new_constants[i] = .{
.name = try allocator.dupe(u8, constant.name),
.value = try allocator.dupe(u8, constant.value),
.comment = if (constant.comment) |c| try allocator.dupe(u8, c) else null,
};
}
return new_constants;
}
fn cloneFields(allocator: Allocator, fields: []const patterns.FieldDecl) ![]patterns.FieldDecl {
const new_fields = try allocator.alloc(patterns.FieldDecl, fields.len);
for (fields, 0..) |field, i| {

View File

@ -1,5 +1,6 @@
const std = @import("std");
const io = @import("io");
const naming = @import("naming.zig");
const Allocator = std.mem.Allocator;
fn fixupZigName(name: []const u8) []u8 {
@ -102,6 +103,7 @@ pub const FlagDecl = struct {
name: []const u8, // SDL_GPUTextureUsageFlags
underlying_type: []const u8, // Uint32
flags: []FlagValue,
constants: []ConstValue, // For non-power-of-2 values
doc_comment: ?[]const u8,
pub fn deinit(self: FlagDecl, allocator: Allocator) void {
@ -114,6 +116,12 @@ pub const FlagDecl = struct {
if (flag.comment) |c| allocator.free(c);
}
allocator.free(self.flags);
for (self.constants) |constant| {
allocator.free(constant.name);
allocator.free(constant.value);
if (constant.comment) |c| allocator.free(c);
}
allocator.free(self.constants);
}
};
@ -123,6 +131,12 @@ pub const FlagValue = struct {
comment: ?[]const u8,
};
pub const ConstValue = struct {
name: []const u8, // SDL_BUTTON_RIGHT
value: []const u8, // 3
comment: ?[]const u8,
};
pub const TypedefDecl = struct {
name: []const u8, // SDL_PropertiesID
underlying_type: []const u8, // Uint32
@ -1132,16 +1146,30 @@ pub const Scanner = struct {
};
const clean_name = std.mem.trimRight(u8, name, ";");
if (!std.mem.endsWith(u8, clean_name, "Flags") and !std.mem.endsWith(u8, clean_name, "Format")) {
// Skip any whitespace/newlines before looking for #define
self.skipWhitespace();
// Look ahead to detect if this is a flag type by checking for #define patterns
const lookahead_pos = self.pos;
const is_bitshift_flag = try self.detectFlagPattern(clean_name);
self.pos = lookahead_pos; // Restore position after detection
// If bitshift detection failed, try suffix-based detection for types ending in "Flags" or "Format"
var is_suffix_flag = false;
if (!is_bitshift_flag and (std.mem.endsWith(u8, clean_name, "Flags") or std.mem.endsWith(u8, clean_name, "Format"))) {
is_suffix_flag = try self.detectSuffixBasedFlags(clean_name);
self.pos = lookahead_pos; // Restore position after detection
}
if (!is_bitshift_flag and !is_suffix_flag) {
self.pos = start;
return null;
}
// Now collect following #define lines
var flags = try std.ArrayList(FlagValue).initCapacity(self.allocator, 10);
// Skip any whitespace/newlines before looking for #define
self.skipWhitespace();
var constants = try std.ArrayList(ConstValue).initCapacity(self.allocator, 10);
// Look ahead for #define lines
while (!self.isAtEnd()) {
@ -1154,13 +1182,14 @@ pub const Scanner = struct {
const define_line = try self.readLine();
defer self.allocator.free(define_line);
if (try self.parseFlagDefine(define_line)) |flag| {
const result = try self.parseFlagOrConstDefine(define_line);
if (result.flag) |flag| {
try flags.append(self.allocator, flag);
} else {
// Not a flag define, restore position
self.pos = define_start;
break;
} else if (result.constant) |constant| {
try constants.append(self.allocator, constant);
}
// If neither flag nor constant, just skip this define and continue
// (don't break - it might be a 0 value or macro that we want to skip)
}
const doc = self.consumePendingDocComment();
@ -1169,17 +1198,260 @@ pub const Scanner = struct {
.name = fixupZigName(clean_name),
.underlying_type = try self.allocator.dupe(u8, underlying),
.flags = try flags.toOwnedSlice(self.allocator),
.constants = try constants.toOwnedSlice(self.allocator),
.doc_comment = doc,
};
}
fn parseFlagDefine(self: *Scanner, line: []const u8) !?FlagValue {
// Format after #define consumed: "SDL_GPU_TEXTUREUSAGE_SAMPLER (1u << 0) /**< comment */"
fn detectFlagPattern(self: *Scanner, typedef_name: []const u8) !bool {
// Look ahead up to 15 lines for #define patterns that indicate this is a flag type
// Criteria:
// 1. Find #define statements with bit shift operators (<<)
// 2. Check if they have a common prefix
// 3. Check if the prefix relates to the typedef name
var bitshift_defines = std.ArrayList([]const u8){};
defer {
for (bitshift_defines.items) |item| {
self.allocator.free(item);
}
bitshift_defines.deinit(self.allocator);
}
var lines_checked: usize = 0;
const max_lines = 15;
while (lines_checked < max_lines and !self.isAtEnd()) {
// Skip whitespace/comments
self.skipWhitespace();
if (self.matchPrefix("#define ")) {
const define_line = try self.readLine();
// Extract the define name (first token)
var parts = std.mem.tokenizeScalar(u8, define_line, ' ');
if (parts.next()) |define_name| {
// Skip macro definitions like SDL_BUTTON_MASK(X)
if (std.mem.indexOf(u8, define_name, "(") != null) {
self.allocator.free(define_line);
lines_checked += 1;
continue;
}
// Check if it contains bit shift operator
if (std.mem.indexOf(u8, define_line, "<<") != null) {
try bitshift_defines.append(self.allocator, try self.allocator.dupe(u8, define_name));
}
}
self.allocator.free(define_line);
} else {
// Not a #define, skip the line
const line = try self.readLine();
self.allocator.free(line);
}
lines_checked += 1;
// Keep collecting defines up to max_lines
// (don't break early - we need to see all potential flags)
}
// Need at least 1 bit-shift define to consider it a bitshift flag type
if (bitshift_defines.items.len < 1) {
return false;
}
// For single define, just check if it relates to the typedef name
if (bitshift_defines.items.len == 1) {
// Skip prefix detection, just verify the define name relates to typedef
return try self.matchesTypedefName(bitshift_defines.items[0], typedef_name);
}
// Check if defines have a common prefix
const common_prefix = try naming.detectCommonPrefix(bitshift_defines.items, self.allocator);
defer self.allocator.free(common_prefix);
if (common_prefix.len == 0) {
return false;
}
// Check if the prefix relates to the typedef name
return self.matchesTypedefName(common_prefix, typedef_name);
}
fn detectSuffixBasedFlags(self: *Scanner, typedef_name: []const u8) !bool {
// For types ending in "Flags", look for simple numeric defines
// Example: SDL_MouseButtonFlags with SDL_BUTTON_LEFT = 1, SDL_BUTTON_MIDDLE = 2
var numeric_defines = std.ArrayList([]const u8){};
defer {
for (numeric_defines.items) |item| {
self.allocator.free(item);
}
numeric_defines.deinit(self.allocator);
}
var lines_checked: usize = 0;
const max_lines = 15;
while (lines_checked < max_lines and !self.isAtEnd()) {
self.skipWhitespace();
if (self.matchPrefix("#define ")) {
const define_line = try self.readLine();
var parts = std.mem.tokenizeScalar(u8, define_line, ' ');
if (parts.next()) |define_name| {
// Skip macro definitions like SDL_BUTTON_MASK(X)
if (std.mem.indexOf(u8, define_name, "(") != null) {
self.allocator.free(define_line);
lines_checked += 1;
continue;
}
// Get the value part
if (parts.next()) |value_part| {
// Check if it's a macro wrapper like SDL_UINT64_C(0x...)
const is_macro_wrapped_hex = blk: {
if (std.mem.startsWith(u8, value_part, "SDL_UINT64_C(") or
std.mem.startsWith(u8, value_part, "SDL_UINT32_C(")) {
// Extract hex value from SDL_UINT64_C(0x0000000000000001)
if (std.mem.indexOf(u8, value_part, "0x")) |hex_start| {
if (std.mem.indexOf(u8, value_part[hex_start..], ")")) |paren_pos| {
const hex_str = value_part[hex_start..hex_start + paren_pos];
_ = std.fmt.parseInt(u64, hex_str, 0) catch break :blk false;
break :blk true;
}
}
}
break :blk false;
};
// Check if it's a plain hex literal like 0x00000010u
const is_plain_hex = blk: {
if (std.mem.indexOf(u8, value_part, "<<") != null) break :blk false;
if (std.mem.indexOf(u8, value_part, "|") != null) break :blk false;
if (std.mem.indexOf(u8, value_part, "SDL_") != null) break :blk false;
// Check if it starts with 0x
if (std.mem.startsWith(u8, value_part, "0x") or std.mem.startsWith(u8, value_part, "0X")) {
// Try to parse as hex (strip trailing 'u' if present)
var hex_val = value_part;
if (std.mem.endsWith(u8, value_part, "u") or std.mem.endsWith(u8, value_part, "U")) {
hex_val = value_part[0..value_part.len - 1];
}
_ = std.fmt.parseInt(u64, hex_val, 0) catch break :blk false;
break :blk true;
}
break :blk false;
};
// Check if it's a simple numeric value (1, 2, 3, etc.)
// Skip if it references other defines or has operators
const is_simple_numeric = blk: {
if (std.mem.indexOf(u8, value_part, "<<") != null) break :blk false;
if (std.mem.indexOf(u8, value_part, "|") != null) break :blk false;
if (std.mem.indexOf(u8, value_part, "(") != null) break :blk false;
if (std.mem.indexOf(u8, value_part, "SDL_") != null) break :blk false;
// Try to parse as integer
_ = std.fmt.parseInt(u32, value_part, 0) catch break :blk false;
break :blk true;
};
if (is_simple_numeric or is_macro_wrapped_hex or is_plain_hex) {
try numeric_defines.append(self.allocator, try self.allocator.dupe(u8, define_name));
}
}
}
self.allocator.free(define_line);
} else {
const line = try self.readLine();
self.allocator.free(line);
}
lines_checked += 1;
// Keep collecting defines up to max_lines
// (don't break early - we need to see all potential flags)
}
// Need at least 1 numeric define
if (numeric_defines.items.len < 1) {
return false;
}
// For single define, just check if it relates to the typedef name
if (numeric_defines.items.len == 1) {
return try self.matchesTypedefName(numeric_defines.items[0], typedef_name);
}
// Check if defines have a common prefix
const common_prefix = try naming.detectCommonPrefix(numeric_defines.items, self.allocator);
defer self.allocator.free(common_prefix);
if (common_prefix.len == 0) {
return false;
}
// Check if the prefix relates to the typedef name
return self.matchesTypedefName(common_prefix, typedef_name);
}
fn matchesTypedefName(self: *Scanner, common_prefix: []const u8, typedef_name: []const u8) !bool {
// Convert typedef name to uppercase and remove SDL_ prefix for comparison
const typedef_upper = try std.ascii.allocUpperString(self.allocator, typedef_name);
defer self.allocator.free(typedef_upper);
const typedef_without_sdl = if (std.mem.startsWith(u8, typedef_upper, "SDL_"))
typedef_upper[4..]
else
typedef_upper;
// Remove common suffixes like FLAGS, FORMAT from typedef for comparison
var typedef_core = typedef_without_sdl;
if (std.mem.endsWith(u8, typedef_core, "FLAGS")) {
typedef_core = typedef_core[0..typedef_core.len - 5];
} else if (std.mem.endsWith(u8, typedef_core, "FORMAT")) {
typedef_core = typedef_core[0..typedef_core.len - 6];
}
// Check if prefix contains the typedef core (ignoring underscores)
// SDL_GPU_TEXTUREUSAGE should match GPUTEXTUREUSAGE
const prefix_no_underscores = try std.mem.replaceOwned(u8, self.allocator, common_prefix, "_", "");
defer self.allocator.free(prefix_no_underscores);
const typedef_no_underscores = try std.mem.replaceOwned(u8, self.allocator, typedef_core, "_", "");
defer self.allocator.free(typedef_no_underscores);
// Remove SDL from prefix if present
const prefix_without_sdl = if (std.mem.startsWith(u8, prefix_no_underscores, "SDL"))
prefix_no_underscores[3..]
else
prefix_no_underscores;
// Check if typedef starts with the prefix (GPU vs GPUTEXTUREUSAGE)
const typedef_starts_with_prefix = std.mem.startsWith(u8, typedef_no_underscores, prefix_without_sdl);
const prefix_starts_with_typedef = std.mem.startsWith(u8, prefix_without_sdl, typedef_no_underscores);
const significant_overlap = prefix_without_sdl.len >= 3 and typedef_no_underscores.len >= prefix_without_sdl.len and
std.mem.startsWith(u8, typedef_no_underscores, prefix_without_sdl);
return typedef_starts_with_prefix or prefix_starts_with_typedef or significant_overlap;
}
const FlagOrConstResult = struct {
flag: ?FlagValue,
constant: ?ConstValue,
};
fn parseFlagOrConstDefine(self: *Scanner, line: []const u8) !FlagOrConstResult {
// Format after #define consumed: "SDL_BUTTON_LEFT 1" or "SDL_GPU_TEXTUREUSAGE_SAMPLER (1u << 0)"
// Note: line doesn't include "#define" - it was already consumed by matchPrefix
// Split by whitespace and get first token (the flag name)
// Split by whitespace and get first token (the define name)
var parts = std.mem.tokenizeScalar(u8, line, ' ');
const name = parts.next() orelse return null;
const name = parts.next() orelse return FlagOrConstResult{ .flag = null, .constant = null };
// Collect the value part (everything until comment)
var value_parts = try std.ArrayList(u8).initCapacity(self.allocator, 32);
@ -1191,7 +1463,7 @@ pub const Scanner = struct {
try value_parts.appendSlice(self.allocator, part);
}
if (value_parts.items.len == 0) return null;
if (value_parts.items.len == 0) return FlagOrConstResult{ .flag = null, .constant = null };
// Extract comment
var comment: ?[]const u8 = null;
@ -1202,11 +1474,139 @@ pub const Scanner = struct {
}
}
return FlagValue{
.name = try self.allocator.dupe(u8, name),
.value = try value_parts.toOwnedSlice(self.allocator),
.comment = comment,
};
const value_str = value_parts.items;
// If it already has << or |, it's a flag define
if (std.mem.indexOf(u8, value_str, "<<") != null) {
return FlagOrConstResult{
.flag = FlagValue{
.name = try self.allocator.dupe(u8, name),
.value = try value_parts.toOwnedSlice(self.allocator),
.comment = comment,
},
.constant = null,
};
}
// Handle SDL_UINT64_C(0x...) or SDL_UINT32_C(0x...) macros
if (std.mem.startsWith(u8, value_str, "SDL_UINT64_C(") or
std.mem.startsWith(u8, value_str, "SDL_UINT32_C(")) {
// Extract hex value
if (std.mem.indexOf(u8, value_str, "0x")) |hex_start| {
if (std.mem.indexOf(u8, value_str[hex_start..], ")")) |paren_pos| {
const hex_str = value_str[hex_start..hex_start + paren_pos];
if (std.fmt.parseInt(u64, hex_str, 0)) |numeric_val| {
if (numeric_val == 0) return FlagOrConstResult{ .flag = null, .constant = null };
// Power of 2: create a flag with bitshift
if (isPowerOfTwo(@intCast(numeric_val))) {
const bit_pos = @ctz(numeric_val);
const converted = try std.fmt.allocPrint(self.allocator, "(1u << {d})", .{bit_pos});
return FlagOrConstResult{
.flag = FlagValue{
.name = try self.allocator.dupe(u8, name),
.value = converted,
.comment = comment,
},
.constant = null,
};
} else {
// Not a power of 2: create a constant
return FlagOrConstResult{
.flag = null,
.constant = ConstValue{
.name = try self.allocator.dupe(u8, name),
.value = try value_parts.toOwnedSlice(self.allocator),
.comment = comment,
},
};
}
} else |_| {}
}
}
// Couldn't parse, skip
return FlagOrConstResult{ .flag = null, .constant = null };
}
// Handle plain hex literals like 0x00000010u
if (std.mem.startsWith(u8, value_str, "0x") or std.mem.startsWith(u8, value_str, "0X")) {
// Strip trailing 'u' or 'U' if present
var hex_val = value_str;
if (std.mem.endsWith(u8, value_str, "u") or std.mem.endsWith(u8, value_str, "U")) {
hex_val = value_str[0..value_str.len - 1];
}
if (std.fmt.parseInt(u64, hex_val, 0)) |numeric_val| {
if (numeric_val == 0) return FlagOrConstResult{ .flag = null, .constant = null };
// Power of 2: create a flag with bitshift
if (isPowerOfTwo(@intCast(numeric_val))) {
const bit_pos = @ctz(numeric_val);
const converted = try std.fmt.allocPrint(self.allocator, "(1u << {d})", .{bit_pos});
return FlagOrConstResult{
.flag = FlagValue{
.name = try self.allocator.dupe(u8, name),
.value = converted,
.comment = comment,
},
.constant = null,
};
} else {
// Not a power of 2: create a constant
return FlagOrConstResult{
.flag = null,
.constant = ConstValue{
.name = try self.allocator.dupe(u8, name),
.value = try value_parts.toOwnedSlice(self.allocator),
.comment = comment,
},
};
}
} else |_| {}
// Couldn't parse, skip
return FlagOrConstResult{ .flag = null, .constant = null };
}
// Skip complex expressions
if (std.mem.indexOf(u8, value_str, "|") != null) return FlagOrConstResult{ .flag = null, .constant = null };
if (std.mem.indexOf(u8, value_str, "(") != null) return FlagOrConstResult{ .flag = null, .constant = null };
if (std.mem.indexOf(u8, value_str, "SDL_") != null) return FlagOrConstResult{ .flag = null, .constant = null };
// Try to parse as integer
if (std.fmt.parseInt(u32, value_str, 0)) |numeric_val| {
if (numeric_val == 0) return FlagOrConstResult{ .flag = null, .constant = null };
// Power of 2: create a flag with bitshift
if (isPowerOfTwo(numeric_val)) {
const bit_pos = @ctz(numeric_val);
const converted = try std.fmt.allocPrint(self.allocator, "(1u << {d})", .{bit_pos});
return FlagOrConstResult{
.flag = FlagValue{
.name = try self.allocator.dupe(u8, name),
.value = converted,
.comment = comment,
},
.constant = null,
};
} else {
// Not a power of 2: create a constant
return FlagOrConstResult{
.flag = null,
.constant = ConstValue{
.name = try self.allocator.dupe(u8, name),
.value = try value_parts.toOwnedSlice(self.allocator),
.comment = comment,
},
};
}
} else |_| {}
// Can't parse, skip
return FlagOrConstResult{ .flag = null, .constant = null };
}
fn isPowerOfTwo(n: u32) bool {
return n != 0 and (n & (n - 1)) == 0;
}
// Pattern: extern SDL_DECLSPEC Type SDLCALL SDL_Name(...);