sdlparser-scrap/AGENTS.md

242 lines
8.3 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 (`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.
### 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.