240 lines
8.1 KiB
Markdown
240 lines
8.1 KiB
Markdown
# 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 orchestration
|
|
- `patterns.zig` - SDL3 pattern matching and declaration scanning
|
|
- `codegen.zig` - Zig code generation
|
|
- `mock_codegen.zig` - C mock generation
|
|
- `json_serializer.zig` - JSON output generation
|
|
- `dependency_resolver.zig` - Type dependency resolution
|
|
- `header_cache.zig` - Header caching for cross-file type resolution
|
|
- `io.zig` - Error handling utilities
|
|
- `types.zig` - Type mapping and conversions
|
|
- `naming.zig` - Naming conventions
|
|
|
|
## Usage
|
|
|
|
### Building and Running
|
|
|
|
**Generate all headers:**
|
|
```bash
|
|
zig build generate
|
|
```
|
|
This processes all SDL3 headers and generates corresponding Zig files in the `api/` directory.
|
|
|
|
**Generate a single header:**
|
|
```bash
|
|
zig build run -- <path-to-header> --output=<output-path>
|
|
```
|
|
|
|
Example:
|
|
```bash
|
|
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.zig`
|
|
- `SDL_video.h` → `video.zig`
|
|
- `SDL_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.zig` suffix)
|
|
- `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:
|
|
|
|
```zig
|
|
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.
|
|
|
|
```zig
|
|
// 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`
|
|
|
|
**Pointer Optionality:**
|
|
- All pointer parameters in standalone functions are optional (`?*const Type`) to allow null values
|
|
- The first parameter in methods (the "self" parameter) is non-optional (`*Type`)
|
|
- Other parameters in methods are optional (`?*const Type`)
|
|
|
|
**Double Pointers to Opaque Types:**
|
|
C double pointers like `SDL_Window **` are converted to `[*c]?*Window` instead of `[*c][*c]Window` because:
|
|
1. Zig doesn't allow C pointers (`[*c]`) to point to opaque types
|
|
2. The inner pointer should be optional since C pointers can be null
|
|
|
|
The outer pointer remains a C pointer for interop, while the inner pointer is an optional Zig pointer.
|
|
|
|
Similarly, `const void * const *` is converted to `[*c]const *anyopaque` (not `[*c]const [*c]const anyopaque`).
|
|
|
|
Example:
|
|
```zig
|
|
// Standalone function - all pointers optional
|
|
pub inline fn beginGPURenderPass(
|
|
gpucommandbuffer: *GPUCommandBuffer,
|
|
color_target_infos: ?*const GPUColorTargetInfo, // optional
|
|
num_color_targets: u32,
|
|
depth_stencil_target_info: ?*const GPUDepthStencilTargetInfo // optional
|
|
) ?*GPURenderPass {
|
|
return @ptrCast(c.SDL_BeginGPURenderPass(...));
|
|
}
|
|
|
|
// Method - self is non-optional, other params are optional
|
|
pub inline fn createGPUTexture(
|
|
gpudevice: *GPUDevice, // non-optional "self"
|
|
createinfo: ?*const GPUTextureCreateInfo // optional
|
|
) ?*GPUTexture {
|
|
return @ptrCast(c.SDL_CreateGPUTexture(@ptrCast(gpudevice), @ptrCast(createinfo)));
|
|
}
|
|
|
|
pub inline fn getWindows(count: *c_int) [*c]?*Window {
|
|
// ^^^^^^^^^^^^ C pointer to optional Zig pointer to opaque type
|
|
return c.SDL_GetWindows(@ptrCast(count));
|
|
}
|
|
```
|
|
|
|
### Validation Process
|
|
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
|
|
4. If fails: debug output written to `debug/` with `_fmterror.zig` suffix
|
|
|
|
**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 declarations
|
|
- `freeDeclDeep()` - 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 of `list.append(item)`
|
|
- Use `list.toOwnedSlice(allocator)` instead of `list.toOwnedSlice()`
|
|
- Use `list.deinit(allocator)` instead of `list.deinit()`
|
|
- Initialize with `std.ArrayList(T){}` (empty init)
|
|
|
|
Example:
|
|
```zig
|
|
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
|
|
// 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
|
|
// 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 flip
|
|
- `FlipMode{ .flipVertical = true }` - vertical flip
|
|
- `FlipMode{ .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.
|