Add union parsing support and fix comment handling in struct/union scanners

- Added UnionDecl type to patterns
- Implemented scanUnion() function similar to scanStruct()
- Added writeUnion() code generation
- Updated all switch statements to handle union_decl
- Fixed multi-line comment detection to handle both /* and /**
- Skip empty enums during code generation
- Update dependency resolver to track union field dependencies
This commit is contained in:
Peterino2 2026-01-22 15:12:47 -08:00
parent 79dd39e36a
commit d270b3fc84
10 changed files with 316 additions and 58 deletions

52
lib/sdl3/apigen.py vendored
View File

@ -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()

135
lib/sdl3/parser/SDL_VIDEO_ANALYSIS.md vendored Normal file
View File

@ -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 <type> *<name>;`
- 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 <return> (SDLCALL *<name>)(<params>);`
- 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.

View File

@ -99,6 +99,7 @@ pub const CodeGen = struct {
.function_pointer_decl => |func_ptr_decl| try self.writeFunctionPointer(func_ptr_decl), .function_pointer_decl => |func_ptr_decl| try self.writeFunctionPointer(func_ptr_decl),
.enum_decl => |enum_decl| try self.writeEnum(enum_decl), .enum_decl => |enum_decl| try self.writeEnum(enum_decl),
.struct_decl => |struct_decl| try self.writeStruct(struct_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), .flag_decl => |flag_decl| try self.writeFlags(flag_decl),
.function_decl => |func| { .function_decl => |func| {
// Only write standalone functions (not methods) // Only write standalone functions (not methods)
@ -206,6 +207,11 @@ pub const CodeGen = struct {
} }
fn writeEnum(self: *CodeGen, enum_decl: EnumDecl) !void { 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); const zig_name = naming.typeNameToZig(enum_decl.name);
// Write doc comment if present // Write doc comment if present
@ -270,6 +276,36 @@ pub const CodeGen = struct {
try self.output.appendSlice(self.allocator, "};\n\n"); 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 { fn writeFlags(self: *CodeGen, flag_decl: FlagDecl) !void {
const zig_name = naming.typeNameToZig(flag_decl.name); const zig_name = naming.typeNameToZig(flag_decl.name);

View File

@ -57,6 +57,7 @@ pub const DependencyResolver = struct {
.function_pointer_decl => |fp| fp.name, .function_pointer_decl => |fp| fp.name,
.enum_decl => |e| e.name, .enum_decl => |e| e.name,
.struct_decl => |s| s.name, .struct_decl => |s| s.name,
.union_decl => |u| u.name,
.flag_decl => |f| f.name, .flag_decl => |f| f.name,
.function_decl => continue, .function_decl => continue,
}; };
@ -84,6 +85,11 @@ pub const DependencyResolver = struct {
try self.scanType(field.type_name); try self.scanType(field.type_name);
} }
}, },
.union_decl => |union_decl| {
for (union_decl.fields) |field| {
try self.scanType(field.type_name);
}
},
else => {}, else => {},
} }
} }
@ -251,6 +257,7 @@ pub fn extractTypeFromHeader(
.typedef_decl => |t| t.name, .typedef_decl => |t| t.name,
.enum_decl => |e| e.name, .enum_decl => |e| e.name,
.struct_decl => |s| s.name, .struct_decl => |s| s.name,
.union_decl => |u| u.name,
.flag_decl => |f| f.name, .flag_decl => |f| f.name,
else => continue, else => continue,
}; };
@ -300,6 +307,13 @@ fn cloneDeclaration(allocator: Allocator, decl: Declaration) !Declaration {
.fields = try cloneFields(allocator, s.fields), .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 => |f| .{
.flag_decl = .{ .flag_decl = .{
.name = try allocator.dupe(u8, f.name), .name = try allocator.dupe(u8, f.name),
@ -407,6 +421,16 @@ fn freeDeclaration(allocator: Allocator, decl: Declaration) void {
} }
allocator.free(s.fields); 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| { .flag_decl => |f| {
allocator.free(f.name); allocator.free(f.name);
allocator.free(f.underlying_type); allocator.free(f.underlying_type);

View File

@ -97,6 +97,16 @@ pub fn main() !void {
} }
allocator.free(struct_decl.fields); 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| { .flag_decl => |flag_decl| {
allocator.free(flag_decl.name); allocator.free(flag_decl.name);
allocator.free(flag_decl.underlying_type); allocator.free(flag_decl.underlying_type);
@ -131,6 +141,7 @@ pub fn main() !void {
var func_ptr_count: usize = 0; var func_ptr_count: usize = 0;
var enum_count: usize = 0; var enum_count: usize = 0;
var struct_count: usize = 0; var struct_count: usize = 0;
var union_count: usize = 0;
var flag_count: usize = 0; var flag_count: usize = 0;
var func_count: usize = 0; var func_count: usize = 0;
@ -141,6 +152,7 @@ pub fn main() !void {
.function_pointer_decl => func_ptr_count += 1, .function_pointer_decl => func_ptr_count += 1,
.enum_decl => enum_count += 1, .enum_decl => enum_count += 1,
.struct_decl => struct_count += 1, .struct_decl => struct_count += 1,
.union_decl => union_count += 1,
.flag_decl => flag_count += 1, .flag_decl => flag_count += 1,
.function_decl => func_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(" - Function pointers: {d}\n", .{func_ptr_count});
std.debug.print(" - Enums: {d}\n", .{enum_count}); std.debug.print(" - Enums: {d}\n", .{enum_count});
std.debug.print(" - Structs: {d}\n", .{struct_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(" - Flags: {d}\n", .{flag_count});
std.debug.print(" - Functions: {d}\n\n", .{func_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); 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| { .flag_decl => |f| {
allocator.free(f.name); allocator.free(f.name);
allocator.free(f.underlying_type); allocator.free(f.underlying_type);

View File

@ -6,6 +6,7 @@ pub const Declaration = union(enum) {
opaque_type: OpaqueType, opaque_type: OpaqueType,
enum_decl: EnumDecl, enum_decl: EnumDecl,
struct_decl: StructDecl, struct_decl: StructDecl,
union_decl: UnionDecl,
flag_decl: FlagDecl, flag_decl: FlagDecl,
function_decl: FunctionDecl, function_decl: FunctionDecl,
typedef_decl: TypedefDecl, typedef_decl: TypedefDecl,
@ -35,6 +36,12 @@ pub const StructDecl = struct {
doc_comment: ?[]const u8, doc_comment: ?[]const u8,
}; };
pub const UnionDecl = struct {
name: []const u8, // SDL_Event
fields: []FieldDecl,
doc_comment: ?[]const u8,
};
pub const FieldDecl = struct { pub const FieldDecl = struct {
name: []const u8, // x name: []const u8, // x
type_name: []const u8, // float type_name: []const u8, // float
@ -111,6 +118,8 @@ pub const Scanner = struct {
try decls.append(self.allocator, .{ .enum_decl = enum_decl }); try decls.append(self.allocator, .{ .enum_decl = enum_decl });
} else if (try self.scanStruct()) |struct_decl| { } else if (try self.scanStruct()) |struct_decl| {
try decls.append(self.allocator, .{ .struct_decl = 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| { } else if (try self.scanFlagTypedef()) |flag_decl| {
// Flag typedef must come before simple typedef // Flag typedef must come before simple typedef
try decls.append(self.allocator, .{ .flag_decl = flag_decl }); try decls.append(self.allocator, .{ .flag_decl = flag_decl });
@ -500,12 +509,12 @@ pub const Scanner = struct {
while (lines.next()) |line| { while (lines.next()) |line| {
const trimmed = std.mem.trim(u8, line, " \t\r"); const trimmed = std.mem.trim(u8, line, " \t\r");
// Track multi-line comments // Track multi-line comments (both /** and /*)
if (std.mem.indexOf(u8, trimmed, "/**")) |_| { if (std.mem.indexOf(u8, trimmed, "/*") != null) {
in_multiline_comment = true; in_multiline_comment = true;
} }
if (in_multiline_comment) { if (in_multiline_comment) {
if (std.mem.indexOf(u8, trimmed, "*/")) |_| { if (std.mem.indexOf(u8, trimmed, "*/") != null) {
in_multiline_comment = false; in_multiline_comment = false;
} }
continue; continue;
@ -514,7 +523,6 @@ pub const Scanner = struct {
// Skip comment/bracket/preprocessor lines // Skip comment/bracket/preprocessor lines
if (trimmed.len == 0) continue; 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; 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 { fn parseStructField(self: *Scanner, line: []const u8) !?FieldDecl {
const trimmed = std.mem.trim(u8, line, " \t\r"); const trimmed = std.mem.trim(u8, line, " \t\r");
if (trimmed.len == 0) return null; if (trimmed.len == 0) return null;

View File

@ -1,3 +1,4 @@
const std = @import("std");
pub const c = @import("c.zig").c; pub const c = @import("c.zig").c;
pub const Window = opaque {}; pub const Window = opaque {};
@ -240,11 +241,13 @@ pub inline fn pushEvent(event: ?*Event) bool {
return c.SDL_PushEvent(event); 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 { pub inline fn setEventFilter(filter: EventFilter, userdata: ?*anyopaque) void {
return c.SDL_SetEventFilter(filter, userdata); 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); return c.SDL_GetEventFilter(filter, userdata);
} }

1
lib/sdl3/v2/gpu.zig vendored
View File

@ -1,3 +1,4 @@
const std = @import("std");
pub const c = @import("c.zig").c; pub const c = @import("c.zig").c;
pub const FColor = extern struct { pub const FColor = extern struct {

View File

@ -1,3 +1,4 @@
const std = @import("std");
pub const c = @import("c.zig").c; pub const c = @import("c.zig").c;
pub const Scancode = enum(c_int) { pub const Scancode = enum(c_int) {
@ -180,7 +181,6 @@ pub const Scancode = enum(c_int) {
scancodeLshift, scancodeLshift,
scancodeRctrl, scancodeRctrl,
scancodeRshift, scancodeRshift,
scancodeMediaSelect,
}; };
pub const Window = opaque { pub const Window = opaque {

View File

@ -1,3 +1,4 @@
const std = @import("std");
pub const c = @import("c.zig").c; pub const c = @import("c.zig").c;
pub const PixelFormat = enum(c_int) { pub const PixelFormat = enum(c_int) {