diff --git a/AGENTS.md b/AGENTS.md index b148314..0bff171 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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. diff --git a/api/events.zig b/api/events.zig index d416a14..f7ddd83 100644 --- a/api/events.zig +++ b/api/events.zig @@ -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) { diff --git a/api/mouse.zig b/api/mouse.zig index 02b4373..381760f 100644 --- a/api/mouse.zig +++ b/api/mouse.zig @@ -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; diff --git a/api/render.zig b/api/render.zig index 6d4b087..b927654 100644 --- a/api/render.zig +++ b/api/render.zig @@ -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) { diff --git a/json/gpu.json b/json/gpu.json index c50fcd7..e0aed6e 100644 --- a/json/gpu.json +++ b/json/gpu.json @@ -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)", diff --git a/json/init.json b/json/init.json index 83fecb9..d315e0e 100644 --- a/json/init.json +++ b/json/init.json @@ -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`" } ] diff --git a/json/messagebox.json b/json/messagebox.json index 3d417d3..d9ab053 100644 --- a/json/messagebox.json +++ b/json/messagebox.json @@ -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" } ] diff --git a/json/mouse.json b/json/mouse.json index 72cd405..b8bbf8b 100644 --- a/json/mouse.json +++ b/json/mouse.json @@ -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)" } ] } diff --git a/json/surface.json b/json/surface.json index 252efe7..642658e 100644 --- a/json/surface.json +++ b/json/surface.json @@ -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()" } ] diff --git a/json/video.json b/json/video.json index cef2a5b..8f81cca 100644 --- a/json/video.json +++ b/json/video.json @@ -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" } ] diff --git a/src/codegen.zig b/src/codegen.zig index 2dd0a85..c0c1554 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -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"); } diff --git a/src/header_cache.zig b/src/header_cache.zig index aec9098..8895963 100644 --- a/src/header_cache.zig +++ b/src/header_cache.zig @@ -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| { diff --git a/src/patterns.zig b/src/patterns.zig index 955f651..a57c31c 100644 --- a/src/patterns.zig +++ b/src/patterns.zig @@ -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 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; + } - fn parseFlagDefine(self: *Scanner, line: []const u8) !?FlagValue { - // Format after #define consumed: "SDL_GPU_TEXTUREUSAGE_SAMPLER (1u << 0) /**< comment */" + 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(...);