6.8 KiB
SDL3 Header Parser - Agent Documentation
Project Overview
This is an SDL3 header parser that converts C header files into Zig interfaces, JSON objects, and C mocks. Built specifically for SDL3 headers (developed against SDL 3.2.10), it focuses on generating Zig APIs.
Key Points:
- Not a real C parser - best-effort text transformation specifically for SDL3 headers
- Does not do proper AST elaboration or tokenization
- Simple to modify but brittle
- Requires Zig 0.15.2 and git
Architecture
The codebase consists of several modules:
parser.zig- Main entry point and orchestrationpatterns.zig- SDL3 pattern matching and declaration scanningcodegen.zig- Zig code generationmock_codegen.zig- C mock generationjson_serializer.zig- JSON output generationdependency_resolver.zig- Type dependency resolutionheader_cache.zig- Header caching for cross-file type resolutionio.zig- Error handling utilitiestypes.zig- Type mapping and conversionsnaming.zig- Naming conventions
Usage
Building and Running
Generate all headers:
zig build generate
This processes all SDL3 headers and generates corresponding Zig files in the api/ directory.
Generate a single header:
zig build run -- <path-to-header> --output=<output-path>
Example:
zig build run -- sdl3\include\SDL3\SDL_gpu.h --output=api\gpu.zig
The parser converts C headers to Zig files:
SDL_gpu.h→gpu.zigSDL_video.h→video.zigSDL_events.h→events.zig
Additional options:
--mocks=<path>- Generate C mock implementations--generate-json=<path>- Generate JSON representation--timestamp=<timestamp>- Set custom timestamp--basedir=<directory>- Set base directory for dependency resolution
Output Directories
api/- Generated Zig API files (final output)tmp/- Temporary files during generation (before validation)debug/- Debug output when generation fails (with_fmterror.zigsuffix)archive/- Archived previous debug outputs
Developing
Build System Patterns
Custom Build Steps: The project uses custom Zig build steps for operations that need to happen during the build process:
const MakeDirStep = struct {
step: std.Build.Step,
dir_path: []const u8,
pub fn create(b: *std.Build, dir_path: []const u8) *std.Build.Step {
const self = b.allocator.create(MakeDirStep) catch @panic("OOM");
self.* = .{
.step = std.Build.Step.init(.{
.id = .custom,
.name = b.fmt("mkdir {s}", .{dir_path}),
.owner = b,
.makeFn = make,
}),
.dir_path = dir_path,
};
return &self.step;
}
fn make(step: *std.Build.Step, options: std.Build.Step.MakeOptions) !void {
_ = options;
const self: *MakeDirStep = @fieldParentPtr("step", step);
try std.fs.cwd().makePath(self.dir_path);
}
};
This pattern allows cross-platform directory creation and other filesystem operations during build without relying on shell commands.
Error Handling
CRITICAL: Whenever actual errors are encountered (parsing failures, invalid declarations, missing types, etc.), you MUST call io.emitError to report them properly. Do NOT use std.debug.print for actual errors.
// Good - proper error reporting
try io.emitError(allocator, "Failed to parse struct field: {s}", .{field_name});
// Bad - don't use for errors
std.debug.print("Error: Failed to parse...\n", .{});
Type Casting in Generated Code
The code generator automatically adds appropriate casts when calling C functions:
Parameters:
- Opaque pointers (
*Window,?*GPUDevice) →@ptrCast - Enums →
@intFromEnum - Flags (packed structs) →
@bitCast - Booleans →
@bitCast
Return values:
- Opaque pointers (
?*Window) →@ptrCast - Booleans →
@bitCast - Flags (packed structs) →
@bitCast
Example:
pub inline fn createWindow(title: [*c]const u8, w: c_int, h: c_int, flags: WindowFlags) ?*Window {
return @ptrCast(c.SDL_CreateWindow(title, w, h, @bitCast(flags)));
// ^^^^^^^^ return cast ^^^^^ param cast
}
Validation Process
After generating Zig code:
- Output written to
tmp/directory zig ast-checkruns on the temporary file- If passes:
zig fmtruns, then copied to final destination - If fails: debug output written to
debug/with_fmterror.zigsuffix
Testing all outputs:
Use zig build generate to regenerate all SDL3 headers and verify no regressions.
Memory Management
- Two cleanup functions exist with identical logic:
freeDecls()- frees a slice of declarationsfreeDeclDeep()- frees a single declaration
- Both free all nested allocations (names, types, doc comments, params/fields/values/flags)
Zig 0.15 Specifics
IMPORTANT: In Zig 0.15, std.ArrayList is an alias to std.ArrayListUnmanaged. This means:
- ArrayList methods require an explicit allocator parameter
- Use
list.append(allocator, item)instead oflist.append(item) - Use
list.toOwnedSlice(allocator)instead oflist.toOwnedSlice() - Use
list.deinit(allocator)instead oflist.deinit() - Initialize with
std.ArrayList(T){}(empty init)
Example:
var list = std.ArrayList(MyType){};
errdefer list.deinit(allocator);
try list.append(allocator, item);
const slice = try list.toOwnedSlice(allocator);
Main APIs (Priority)
Focus areas: gpu, video, gamepad, joystick, input, event
Anything beyond these is not actively maintained.
Code Generation Features
Enum-to-Flags Conversion
The parser automatically detects enums that contain bitwise OR operations and converts them to packed structs with bool flags.
Example: SDL_FlipMode in SDL_surface.h
// C enum with bitwise OR
typedef enum SDL_FlipMode {
SDL_FLIP_NONE, // 0
SDL_FLIP_HORIZONTAL, // 1
SDL_FLIP_VERTICAL, // 2
SDL_FLIP_HORIZONTAL_AND_VERTICAL = (SDL_FLIP_HORIZONTAL | SDL_FLIP_VERTICAL) // 3
} SDL_FlipMode;
Converts to:
// Zig packed struct with bool flags
pub const FlipMode = packed struct(u32) {
flipHorizontal: bool = false,
flipVertical: bool = false,
pad0: u29 = 0,
rsvd: bool = false,
pub const None = FlipMode{};
};
Usage:
FlipMode.None- no flip (matches C's SDL_FLIP_NONE)FlipMode{ .flipHorizontal = true }- horizontal flipFlipMode{ .flipVertical = true }- vertical flipFlipMode{ .flipHorizontal = true, .flipVertical = true }- both (equivalent to SDL_FLIP_HORIZONTAL_AND_VERTICAL)
This approach eliminates the need for explicit combined values and provides a type-safe way to combine flags.