diff --git a/lib/sdl3/apigen.py b/lib/sdl3/apigen.py deleted file mode 100644 index d82a1ad..0000000 --- a/lib/sdl3/apigen.py +++ /dev/null @@ -1,52 +0,0 @@ -import os -import time -import json -import subprocess - -class HeaderParser: - - def __init__(self, headerList, target, headerName): - self.headerList = headerList - self.path = target - self.headerName = headerName - self.outputPrefix = "asts/" + headerName - self.astJsonFile = os.path.join(orig_dir, self.headerName + ".ast.json") - with open(self.astJsonFile) as f: - self.jsonRepr = json.load(f) - -orig_dir = os.path.abspath(os.path.dirname(__file__)) -inputList = [] - -def parseAll(sdl3IncludePath, headerList): - parsedList = [] - for f in headerList: - if f.endswith(".h"): - headerName = f.split(".")[0] - if "_" in headerName: - headerName = headerName.split('_')[1] - - print(headerName) - parsedList.append(os.path.join(sdl3IncludePath, f)) - parsePath = os.path.join(sdl3IncludePath, 'SDL_gpu.h') - # os.system(f"cheader2json convert {f} --prefix={self.headerName}") - -def parse(): - global inputList - sdl3IncludePath = os.path.join(orig_dir, 'SDL/include/SDL3') - discoveredFiles = os.listdir(os.path.join(orig_dir, 'SDL/include/SDL3')) - - parsedList = [] - - parseAll(sdl3IncludePath, discoveredFiles) - - sdlHeaderList = [] - for file in discoveredFiles: - sdlHeaderList.append(os.path.join(sdl3IncludePath, file)) - - parsed = HeaderParser(sdlHeaderList, os.path.join(sdl3IncludePath, 'SDL_gpu.h'), "gpu") - - print("parsing list: ", inputList) - -if __name__ == "__main__": - while True: - parse() diff --git a/lib/sdl3/parser/SDL_VIDEO_ANALYSIS.md b/lib/sdl3/parser/SDL_VIDEO_ANALYSIS.md new file mode 100644 index 0000000..194036a --- /dev/null +++ b/lib/sdl3/parser/SDL_VIDEO_ANALYSIS.md @@ -0,0 +1,135 @@ +# SDL_video.h Parsing Analysis + +## Current Status: ✅ MOSTLY WORKING + +The SDL_video.h header parses successfully with only minor missing type warnings. All major functionality is captured. + +## Statistics + +- **Total declarations found**: 124 + - Opaque types: 2 + - Typedefs: 6 + - Function pointers: 0 + - Enums: 4 + - Structs: 2 + - Flags: 1 + - Functions: 109 + +## Successfully Resolved Dependencies + +The parser successfully resolves and imports these types from dependency headers: + +✅ **SDL_PixelFormat** (from SDL_pixels.h) +✅ **SDL_Point** (from SDL_rect.h) +✅ **SDL_Rect** (from SDL_rect.h) +✅ **SDL_Surface** (from SDL_surface.h) +✅ **SDL_PropertiesID** (from SDL_properties.h) + +## Missing Type Definitions (7 types) + +These types are referenced but not found in the included headers: + +### 1. EGL-Related Types (5 types) + +These are OpenGL ES/EGL integration types defined within SDL_video.h itself: + +- **SDL_EGLConfig** - `typedef void *SDL_EGLConfig;` +- **SDL_EGLDisplay** - `typedef void *SDL_EGLDisplay;` +- **SDL_EGLSurface** - `typedef void *SDL_EGLSurface;` +- **SDL_EGLAttribArrayCallback** - Function pointer typedef +- **SDL_EGLIntArrayCallback** - Function pointer typedef + +**Root Cause**: These are defined in SDL_video.h but the parser's typedef scanner is not picking them up properly. + +**Issue**: The typedef scanner currently only processes simple typedefs and doesn't handle: +- Pointer typedefs (`typedef void *Type;`) +- Function pointer typedefs with complex signatures + +### 2. OpenGL Types (2 types) + +- **SDL_GLAttr** - Enum type for GL attributes +- **SDL_GLContext** - `typedef struct SDL_GLContextState *SDL_GLContext;` + +**Root Cause**: Similar to EGL types - these are typedef'd in SDL_video.h but not captured by the scanner. + +### 3. Callback Types (1 type) + +- **SDL_HitTest** - `typedef SDL_HitTestResult (SDLCALL *SDL_HitTest)(...);` + +**Root Cause**: Function pointer typedef with calling convention modifier. + +### 4. Generic Types (1 type) + +- **SDL_FunctionPointer** - `typedef void (SDLCALL *SDL_FunctionPointer)(void);` + +**Root Cause**: Function pointer typedef. + +## Implementation Plan + +### Phase 1: Enhance Typedef Scanner ✅ PRIORITY + +**Goal**: Make the typedef scanner capture all typedef forms in the same file being parsed. + +**Tasks**: + +1. **Add pointer typedef support** + ```c + typedef void *SDL_EGLConfig; + typedef struct SDL_GLContextState *SDL_GLContext; + ``` + - Pattern: `typedef *;` + - Store as opaque pointer type + +2. **Add function pointer typedef support** + ```c + typedef SDL_HitTestResult (SDLCALL *SDL_HitTest)(SDL_Window *win, const SDL_Point *area, void *data); + typedef void (SDLCALL *SDL_FunctionPointer)(void); + ``` + - Pattern: `typedef (SDLCALL *)();` + - Store as function pointer type with signature + +3. **Add enum typedef support** + ```c + typedef enum SDL_GLAttr { ... } SDL_GLAttr; + ``` + - Pattern: Already handled, but verify it works for GL types + +**Implementation Location**: `src/dependency_resolver.zig` - `scanFileForTypedefs()` + +**Expected Result**: After this phase, all 7 missing types should be found and properly typed. + +### Phase 2: Test and Validate + +1. Run parser on SDL_video.h +2. Verify all 14 originally missing types are now resolved (7 from deps, 7 from typedefs) +3. Verify generated Zig code compiles +4. Check that function signatures using these types are correct + +### Phase 3: Apply to Other Headers + +Once SDL_video.h parses completely clean, apply the same pattern to other headers with similar issues. + +## Error Categories + +### Category A: Typedef Scanner Limitations ⭐ PRIMARY ISSUE +- **Impact**: 7/14 missing types (50%) +- **Difficulty**: Medium +- **Files affected**: SDL_video.h, potentially others +- **Solution**: Enhance typedef scanner (Phase 1) + +### Category B: Cross-header Dependencies ✅ SOLVED +- **Impact**: 7/14 missing types (50%) - but these work! +- **Difficulty**: N/A (already working) +- **Solution**: Existing dependency resolver handles this correctly + +## Success Metrics + +After implementing Phase 1: +- ⬜ Zero "Could not find definition" warnings for SDL_video.h +- ⬜ Generated code compiles without errors +- ⬜ All 124 declarations properly typed +- ⬜ Can use as template for other complex headers + +## Notes + +The current parsing system is quite robust. The main gap is in the typedef scanner not recognizing all forms of typedef. This is a focused, solvable problem that will unlock SDL_video.h and similar headers. diff --git a/lib/sdl3/parser/src/codegen.zig b/lib/sdl3/parser/src/codegen.zig index a1df115..7c89def 100644 --- a/lib/sdl3/parser/src/codegen.zig +++ b/lib/sdl3/parser/src/codegen.zig @@ -99,6 +99,7 @@ pub const CodeGen = struct { .function_pointer_decl => |func_ptr_decl| try self.writeFunctionPointer(func_ptr_decl), .enum_decl => |enum_decl| try self.writeEnum(enum_decl), .struct_decl => |struct_decl| try self.writeStruct(struct_decl), + .union_decl => |union_decl| try self.writeUnion(union_decl), .flag_decl => |flag_decl| try self.writeFlags(flag_decl), .function_decl => |func| { // Only write standalone functions (not methods) @@ -206,6 +207,11 @@ pub const CodeGen = struct { } fn writeEnum(self: *CodeGen, enum_decl: EnumDecl) !void { + // Skip empty enums + if (enum_decl.values.len == 0) { + return; + } + const zig_name = naming.typeNameToZig(enum_decl.name); // Write doc comment if present @@ -270,6 +276,36 @@ pub const CodeGen = struct { try self.output.appendSlice(self.allocator, "};\n\n"); } + fn writeUnion(self: *CodeGen, union_decl: patterns.UnionDecl) !void { + const zig_name = naming.typeNameToZig(union_decl.name); + + // Write doc comment if present + if (union_decl.doc_comment) |doc| { + try self.writeDocComment(doc); + } + + // pub const Event = extern union { + try self.output.writer(self.allocator).print("pub const {s} = extern union {{\n", .{zig_name}); + + // Write fields + for (union_decl.fields) |field| { + const zig_type = try types.convertType(field.type_name, self.allocator); + defer self.allocator.free(zig_type); + + if (field.comment) |comment| { + try self.output.writer(self.allocator).print(" {s}: {s}, // {s}\n", .{ + field.name, + zig_type, + comment, + }); + } else { + try self.output.writer(self.allocator).print(" {s}: {s},\n", .{ field.name, zig_type }); + } + } + + try self.output.appendSlice(self.allocator, "};\n\n"); + } + fn writeFlags(self: *CodeGen, flag_decl: FlagDecl) !void { const zig_name = naming.typeNameToZig(flag_decl.name); diff --git a/lib/sdl3/parser/src/dependency_resolver.zig b/lib/sdl3/parser/src/dependency_resolver.zig index b023509..cbb84e5 100644 --- a/lib/sdl3/parser/src/dependency_resolver.zig +++ b/lib/sdl3/parser/src/dependency_resolver.zig @@ -57,6 +57,7 @@ pub const DependencyResolver = struct { .function_pointer_decl => |fp| fp.name, .enum_decl => |e| e.name, .struct_decl => |s| s.name, + .union_decl => |u| u.name, .flag_decl => |f| f.name, .function_decl => continue, }; @@ -84,6 +85,11 @@ pub const DependencyResolver = struct { try self.scanType(field.type_name); } }, + .union_decl => |union_decl| { + for (union_decl.fields) |field| { + try self.scanType(field.type_name); + } + }, else => {}, } } @@ -251,6 +257,7 @@ pub fn extractTypeFromHeader( .typedef_decl => |t| t.name, .enum_decl => |e| e.name, .struct_decl => |s| s.name, + .union_decl => |u| u.name, .flag_decl => |f| f.name, else => continue, }; @@ -300,6 +307,13 @@ fn cloneDeclaration(allocator: Allocator, decl: Declaration) !Declaration { .fields = try cloneFields(allocator, s.fields), }, }, + .union_decl => |u| .{ + .union_decl = .{ + .name = try allocator.dupe(u8, u.name), + .doc_comment = if (u.doc_comment) |doc| try allocator.dupe(u8, doc) else null, + .fields = try cloneFields(allocator, u.fields), + }, + }, .flag_decl => |f| .{ .flag_decl = .{ .name = try allocator.dupe(u8, f.name), @@ -407,6 +421,16 @@ fn freeDeclaration(allocator: Allocator, decl: Declaration) void { } allocator.free(s.fields); }, + .union_decl => |u| { + allocator.free(u.name); + if (u.doc_comment) |doc| allocator.free(doc); + for (u.fields) |field| { + allocator.free(field.name); + allocator.free(field.type_name); + if (field.comment) |c| allocator.free(c); + } + allocator.free(u.fields); + }, .flag_decl => |f| { allocator.free(f.name); allocator.free(f.underlying_type); diff --git a/lib/sdl3/parser/src/parser.zig b/lib/sdl3/parser/src/parser.zig index f0afb48..d5e3e4b 100644 --- a/lib/sdl3/parser/src/parser.zig +++ b/lib/sdl3/parser/src/parser.zig @@ -97,6 +97,16 @@ pub fn main() !void { } allocator.free(struct_decl.fields); }, + .union_decl => |union_decl| { + allocator.free(union_decl.name); + if (union_decl.doc_comment) |doc| allocator.free(doc); + for (union_decl.fields) |field| { + allocator.free(field.name); + allocator.free(field.type_name); + if (field.comment) |c| allocator.free(c); + } + allocator.free(union_decl.fields); + }, .flag_decl => |flag_decl| { allocator.free(flag_decl.name); allocator.free(flag_decl.underlying_type); @@ -131,6 +141,7 @@ pub fn main() !void { var func_ptr_count: usize = 0; var enum_count: usize = 0; var struct_count: usize = 0; + var union_count: usize = 0; var flag_count: usize = 0; var func_count: usize = 0; @@ -141,6 +152,7 @@ pub fn main() !void { .function_pointer_decl => func_ptr_count += 1, .enum_decl => enum_count += 1, .struct_decl => struct_count += 1, + .union_decl => union_count += 1, .flag_decl => flag_count += 1, .function_decl => func_count += 1, } @@ -151,6 +163,7 @@ pub fn main() !void { std.debug.print(" - Function pointers: {d}\n", .{func_ptr_count}); std.debug.print(" - Enums: {d}\n", .{enum_count}); std.debug.print(" - Structs: {d}\n", .{struct_count}); + std.debug.print(" - Unions: {d}\n", .{union_count}); std.debug.print(" - Flags: {d}\n", .{flag_count}); std.debug.print(" - Functions: {d}\n\n", .{func_count}); @@ -392,6 +405,16 @@ fn freeDeclDeep(allocator: std.mem.Allocator, decl: patterns.Declaration) void { } allocator.free(s.fields); }, + .union_decl => |u| { + allocator.free(u.name); + if (u.doc_comment) |doc| allocator.free(doc); + for (u.fields) |field| { + allocator.free(field.name); + allocator.free(field.type_name); + if (field.comment) |c| allocator.free(c); + } + allocator.free(u.fields); + }, .flag_decl => |f| { allocator.free(f.name); allocator.free(f.underlying_type); diff --git a/lib/sdl3/parser/src/patterns.zig b/lib/sdl3/parser/src/patterns.zig index 5429074..e5f59b6 100644 --- a/lib/sdl3/parser/src/patterns.zig +++ b/lib/sdl3/parser/src/patterns.zig @@ -6,6 +6,7 @@ pub const Declaration = union(enum) { opaque_type: OpaqueType, enum_decl: EnumDecl, struct_decl: StructDecl, + union_decl: UnionDecl, flag_decl: FlagDecl, function_decl: FunctionDecl, typedef_decl: TypedefDecl, @@ -35,6 +36,12 @@ pub const StructDecl = struct { doc_comment: ?[]const u8, }; +pub const UnionDecl = struct { + name: []const u8, // SDL_Event + fields: []FieldDecl, + doc_comment: ?[]const u8, +}; + pub const FieldDecl = struct { name: []const u8, // x type_name: []const u8, // float @@ -111,6 +118,8 @@ pub const Scanner = struct { try decls.append(self.allocator, .{ .enum_decl = enum_decl }); } else if (try self.scanStruct()) |struct_decl| { try decls.append(self.allocator, .{ .struct_decl = struct_decl }); + } else if (try self.scanUnion()) |union_decl| { + try decls.append(self.allocator, .{ .union_decl = union_decl }); } else if (try self.scanFlagTypedef()) |flag_decl| { // Flag typedef must come before simple typedef try decls.append(self.allocator, .{ .flag_decl = flag_decl }); @@ -500,12 +509,12 @@ pub const Scanner = struct { while (lines.next()) |line| { const trimmed = std.mem.trim(u8, line, " \t\r"); - // Track multi-line comments - if (std.mem.indexOf(u8, trimmed, "/**")) |_| { + // Track multi-line comments (both /** and /*) + if (std.mem.indexOf(u8, trimmed, "/*") != null) { in_multiline_comment = true; } if (in_multiline_comment) { - if (std.mem.indexOf(u8, trimmed, "*/")) |_| { + if (std.mem.indexOf(u8, trimmed, "*/") != null) { in_multiline_comment = false; } continue; @@ -514,7 +523,6 @@ pub const Scanner = struct { // Skip comment/bracket/preprocessor lines if (trimmed.len == 0) continue; if (std.mem.startsWith(u8, trimmed, "//")) continue; - if (std.mem.startsWith(u8, trimmed, "/*")) continue; if (std.mem.startsWith(u8, trimmed, "*")) continue; if (std.mem.startsWith(u8, trimmed, "#")) continue; @@ -542,6 +550,85 @@ pub const Scanner = struct { }; } + fn scanUnion(self: *Scanner) !?UnionDecl { + const start = self.pos; + + if (!self.matchPrefix("typedef union ")) { + return null; + } + + // Find the opening brace and extract the name before it + const name_start = self.pos; + while (self.pos < self.source.len and self.source[self.pos] != '{') { + self.pos += 1; + } + + if (self.pos >= self.source.len) { + // No opening brace found - this is an opaque type, not a union + self.pos = start; + return null; + } + + // Extract name from between "typedef union " and "{" + const name_slice = std.mem.trim(u8, self.source[name_start..self.pos], " \t\n\r"); + var iter = std.mem.tokenizeScalar(u8, name_slice, ' '); + const name = iter.next() orelse { + self.pos = start; + return null; + }; + + // Now we're at the opening brace, read the braced block + const body = try self.readBracedBlock(); + defer self.allocator.free(body); + + // Parse fields + var fields = try std.ArrayList(FieldDecl).initCapacity(self.allocator, 20); + var lines = std.mem.splitScalar(u8, body, '\n'); + var in_multiline_comment = false; + + while (lines.next()) |line| { + const trimmed = std.mem.trim(u8, line, " \t\r"); + + // Track multi-line comments (both /** and /*) + if (std.mem.indexOf(u8, trimmed, "/*") != null) { + in_multiline_comment = true; + } + if (in_multiline_comment) { + if (std.mem.indexOf(u8, trimmed, "*/") != null) { + in_multiline_comment = false; + } + continue; + } + + // Skip comment/bracket/preprocessor lines + if (trimmed.len == 0) continue; + if (std.mem.startsWith(u8, trimmed, "//")) continue; + if (std.mem.startsWith(u8, trimmed, "*")) continue; + if (std.mem.startsWith(u8, trimmed, "#")) continue; + + // Reuse struct field parsing since unions have same field syntax + if (try self.parseStructField(line)) |field| { + try fields.append(self.allocator, field); + } else { + const multi_fields = try self.parseMultiFieldLine(line); + if (multi_fields.len > 0) { + for (multi_fields) |field| { + try fields.append(self.allocator, field); + } + self.allocator.free(multi_fields); + } + } + } + + const doc = self.consumePendingDocComment(); + + return UnionDecl{ + .name = try self.allocator.dupe(u8, name), + .fields = try fields.toOwnedSlice(self.allocator), + .doc_comment = doc, + }; + } + fn parseStructField(self: *Scanner, line: []const u8) !?FieldDecl { const trimmed = std.mem.trim(u8, line, " \t\r"); if (trimmed.len == 0) return null; diff --git a/lib/sdl3/v2/events.zig b/lib/sdl3/v2/events.zig index da9dfc3..665164c 100644 --- a/lib/sdl3/v2/events.zig +++ b/lib/sdl3/v2/events.zig @@ -1,3 +1,4 @@ +const std = @import("std"); pub const c = @import("c.zig").c; pub const Window = opaque {}; @@ -240,11 +241,13 @@ pub inline fn pushEvent(event: ?*Event) bool { return c.SDL_PushEvent(event); } +pub const EventFilter = *const fn(userdata: ?*anyopaque, event: ?*Event) callconv(.C) bool; + pub inline fn setEventFilter(filter: EventFilter, userdata: ?*anyopaque) void { return c.SDL_SetEventFilter(filter, userdata); } -pub inline fn getEventFilter(filter: ?*EventFilter, userdata: void **) bool { +pub inline fn getEventFilter(filter: ?*EventFilter, userdata: [*c]?*anyopaque) bool { return c.SDL_GetEventFilter(filter, userdata); } diff --git a/lib/sdl3/v2/gpu.zig b/lib/sdl3/v2/gpu.zig index a2daefb..54e479b 100644 --- a/lib/sdl3/v2/gpu.zig +++ b/lib/sdl3/v2/gpu.zig @@ -1,3 +1,4 @@ +const std = @import("std"); pub const c = @import("c.zig").c; pub const FColor = extern struct { diff --git a/lib/sdl3/v2/keyboard.zig b/lib/sdl3/v2/keyboard.zig index bed8318..f7c7b78 100644 --- a/lib/sdl3/v2/keyboard.zig +++ b/lib/sdl3/v2/keyboard.zig @@ -1,3 +1,4 @@ +const std = @import("std"); pub const c = @import("c.zig").c; pub const Scancode = enum(c_int) { @@ -180,7 +181,6 @@ pub const Scancode = enum(c_int) { scancodeLshift, scancodeRctrl, scancodeRshift, - scancodeMediaSelect, }; pub const Window = opaque { diff --git a/lib/sdl3/v2/video.zig b/lib/sdl3/v2/video.zig index bf4b08d..2847792 100644 --- a/lib/sdl3/v2/video.zig +++ b/lib/sdl3/v2/video.zig @@ -1,3 +1,4 @@ +const std = @import("std"); pub const c = @import("c.zig").c; pub const PixelFormat = enum(c_int) {