Compare commits

...

2 Commits

10 changed files with 237 additions and 422 deletions

56
AGENTS.md Normal file
View File

@ -0,0 +1,56 @@
# 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
## Developing
### 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", .{});
```
### 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
### 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)
### Main APIs (Priority)
Focus areas: gpu, video, gamepad, joystick, input, event
Anything beyond these is not actively maintained.

View File

@ -9,34 +9,6 @@
"unions": [],
"flags": [],
"functions": [
{
"name": "SDL_SetError",
"return_type": "bool",
"parameters": [
{
"name": "fmt",
"type": "const char *"
},
{
"name": "",
"type": "..."
}
]
},
{
"name": "SDL_SetErrorV",
"return_type": "bool",
"parameters": [
{
"name": "fmt",
"type": "const char *"
},
{
"name": "ap",
"type": "va_list"
}
]
},
{
"name": "SDL_OutOfMemory",
"return_type": "bool",

View File

@ -1638,32 +1638,6 @@
"type": "const char *"
}
]
},
{
"name": "SDL_RenderDebugTextFormat",
"return_type": "bool",
"parameters": [
{
"name": "renderer",
"type": "SDL_Renderer *"
},
{
"name": "x",
"type": "float"
},
{
"name": "y",
"type": "float"
},
{
"name": "fmt",
"type": "const char *"
},
{
"name": "",
"type": "..."
}
]
}
]
}

View File

@ -49,14 +49,6 @@ pub const DependencyResolver = struct {
return try missing.toOwnedSlice(allocator);
}
/// Get hardcoded declarations for special types that can't be resolved from headers
/// Currently unused - function pointer typedefs are now handled as c_type_alias in patterns
pub fn getHardcodedDeclarations(self: *DependencyResolver, allocator: Allocator) ![]Declaration {
_ = self;
var hardcoded = std.ArrayList(Declaration){};
return try hardcoded.toOwnedSlice(allocator);
}
fn collectDefinedTypes(self: *DependencyResolver, decls: []const Declaration) !void {
for (decls) |decl| {
const type_name = switch (decl) {

View File

@ -11,4 +11,14 @@ pub fn initWriter() void {
stdout = &stdout_writer.interface;
}
pub fn emitError(src: std.builtin.SourceLocation, errorMessage: []const u8) void {
std.debug.print("----- Error occured -----\n", .{});
std.debug.print("in {s}:{d} module: {s}", .{ src.file, src.line, src.module });
std.debug.print("function: {s}", .{src.fn_name});
std.debug.print("message: '{s}'", .{errorMessage});
std.debug.dumpCurrentStackTrace(null);
std.debug.print("---- /Error occured -----\n", .{});
}
pub const std = @import("std");
const buildOptions = @import("buildOptions");

View File

@ -6,6 +6,13 @@ const json_serializer = @import("json_serializer.zig");
const io = @import("io.zig");
const header_cache = @import("header_cache.zig");
pub fn freeDecls(allocator: std.mem.Allocator, decls: []patterns.Declaration) void {
for (decls) |decl| {
freeDeclDeep(allocator, decl);
}
allocator.free(decls);
}
pub fn main() !void {
const allocator = std.heap.smp_allocator;
@ -74,87 +81,7 @@ pub fn main() !void {
// Parse declarations
var scanner = patterns.Scanner.init(allocator, source);
const decls = try scanner.scan();
defer {
for (decls) |decl| {
switch (decl) {
.opaque_type => |opaque_decl| {
allocator.free(opaque_decl.name);
if (opaque_decl.doc_comment) |doc| allocator.free(doc);
},
.typedef_decl => |typedef_decl| {
allocator.free(typedef_decl.name);
allocator.free(typedef_decl.underlying_type);
if (typedef_decl.doc_comment) |doc| allocator.free(doc);
},
.function_pointer_decl => |func_ptr_decl| {
allocator.free(func_ptr_decl.name);
allocator.free(func_ptr_decl.return_type);
if (func_ptr_decl.doc_comment) |doc| allocator.free(doc);
for (func_ptr_decl.params) |param| {
allocator.free(param.name);
allocator.free(param.type_name);
}
allocator.free(func_ptr_decl.params);
},
.c_type_alias => |alias| {
allocator.free(alias.name);
if (alias.doc_comment) |doc| allocator.free(doc);
},
.enum_decl => |enum_decl| {
allocator.free(enum_decl.name);
if (enum_decl.doc_comment) |doc| allocator.free(doc);
for (enum_decl.values) |val| {
allocator.free(val.name);
if (val.value) |v| allocator.free(v);
if (val.comment) |c| allocator.free(c);
}
allocator.free(enum_decl.values);
},
.struct_decl => |struct_decl| {
allocator.free(struct_decl.name);
if (struct_decl.doc_comment) |doc| allocator.free(doc);
for (struct_decl.fields) |field| {
allocator.free(field.name);
allocator.free(field.type_name);
if (field.comment) |c| allocator.free(c);
}
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);
if (flag_decl.doc_comment) |doc| allocator.free(doc);
for (flag_decl.flags) |flag| {
allocator.free(flag.name);
allocator.free(flag.value);
if (flag.comment) |c| allocator.free(c);
}
allocator.free(flag_decl.flags);
},
.function_decl => |func| {
allocator.free(func.name);
allocator.free(func.return_type);
if (func.doc_comment) |doc| allocator.free(doc);
for (func.params) |param| {
allocator.free(param.name);
allocator.free(param.type_name);
}
allocator.free(func.params);
},
}
}
allocator.free(decls);
}
defer freeDecls(allocator, decls);
std.debug.print("Found {d} declarations\n", .{decls.len});
@ -240,17 +167,17 @@ pub fn main() !void {
}
// Get hardcoded declarations for special types (like SDL_HitTest)
const hardcoded_decls = try resolver.getHardcodedDeclarations(allocator);
defer {
for (hardcoded_decls) |hd| {
freeDeclDeep(allocator, hd);
}
allocator.free(hardcoded_decls);
}
// const hardcoded_decls = try resolver.getHardcodedDeclarations(allocator);
// defer {
// for (hardcoded_decls) |hd| {
// freeDeclDeep(allocator, hd);
// }
// allocator.free(hardcoded_decls);
// }
if (hardcoded_decls.len > 0) {
std.debug.print("Adding {d} hardcoded type declarations\n", .{hardcoded_decls.len});
}
// if (hardcoded_decls.len > 0) {
// std.debug.print("Adding {d} hardcoded type declarations\n", .{hardcoded_decls.len});
// }
var dependency_decls = std.ArrayList(patterns.Declaration){};
defer {
@ -275,7 +202,7 @@ pub fn main() !void {
// PHASE 2: Resolve dependencies via cache lookup with recursive dependency resolution
std.debug.print("Resolving dependencies from cache...\n", .{});
// Track which types we've already added to avoid duplicates
var added_types = std.StringHashMap(void).init(allocator);
defer {
@ -285,17 +212,17 @@ pub fn main() !void {
}
added_types.deinit();
}
for (missing_types) |missing_type| {
// Skip if already added
if (added_types.contains(missing_type)) continue;
// Look up type with all its dependencies
const resolved_decls = cache.lookupTypeWithDependencies(missing_type) catch |err| {
std.debug.print(" Warning: Could not resolve {s}: {}\n", .{ missing_type, err });
continue;
};
// Add all resolved declarations
for (resolved_decls) |resolved_decl| {
const decl_type_name = switch (resolved_decl) {
@ -309,11 +236,11 @@ pub fn main() !void {
.flag_decl => |f| f.name,
.function_decl => continue, // Skip functions
};
if (!added_types.contains(decl_type_name)) {
try dependency_decls.append(allocator, resolved_decl);
try added_types.put(try allocator.dupe(u8, decl_type_name), {});
if (std.mem.eql(u8, decl_type_name, missing_type)) {
std.debug.print(" Found {s} in cache\n", .{missing_type});
} else {
@ -324,7 +251,7 @@ pub fn main() !void {
freeDeclDeep(allocator, resolved_decl);
}
}
allocator.free(resolved_decls);
}
@ -332,12 +259,12 @@ pub fn main() !void {
}
// Combine declarations (hardcoded first, then dependencies, then primary!)
std.debug.print("Combining {d} hardcoded + {d} dependency declarations with primary declarations...\n", .{ hardcoded_decls.len, dependency_decls.items.len });
std.debug.print("Combining {d} dependency declarations with primary declarations...\n", .{dependency_decls.items.len});
var all_decls = std.ArrayList(patterns.Declaration){};
defer all_decls.deinit(allocator);
try all_decls.appendSlice(allocator, hardcoded_decls);
// try all_decls.appendSlice(allocator, hardcoded_decls);
try all_decls.appendSlice(allocator, dependency_decls.items);
try all_decls.appendSlice(allocator, decls);
@ -535,80 +462,7 @@ fn writeDebugFile(allocator: std.mem.Allocator, header_path: []const u8, output:
fn freeDeclDeep(allocator: std.mem.Allocator, decl: patterns.Declaration) void {
switch (decl) {
.opaque_type => |o| {
allocator.free(o.name);
if (o.doc_comment) |doc| allocator.free(doc);
},
.typedef_decl => |t| {
allocator.free(t.name);
allocator.free(t.underlying_type);
if (t.doc_comment) |doc| allocator.free(doc);
},
.function_pointer_decl => |fp| {
allocator.free(fp.name);
allocator.free(fp.return_type);
if (fp.doc_comment) |doc| allocator.free(doc);
for (fp.params) |param| {
allocator.free(param.name);
allocator.free(param.type_name);
}
allocator.free(fp.params);
},
.c_type_alias => |a| {
allocator.free(a.name);
if (a.doc_comment) |doc| allocator.free(doc);
},
.enum_decl => |e| {
allocator.free(e.name);
if (e.doc_comment) |doc| allocator.free(doc);
for (e.values) |val| {
allocator.free(val.name);
if (val.value) |v| allocator.free(v);
if (val.comment) |c| allocator.free(c);
}
allocator.free(e.values);
},
.struct_decl => |s| {
allocator.free(s.name);
if (s.doc_comment) |doc| allocator.free(doc);
for (s.fields) |field| {
allocator.free(field.name);
allocator.free(field.type_name);
if (field.comment) |c| allocator.free(c);
}
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);
if (f.doc_comment) |doc| allocator.free(doc);
for (f.flags) |flag| {
allocator.free(flag.name);
allocator.free(flag.value);
if (flag.comment) |c| allocator.free(c);
}
allocator.free(f.flags);
},
.function_decl => |func| {
allocator.free(func.name);
allocator.free(func.return_type);
if (func.doc_comment) |doc| allocator.free(doc);
for (func.params) |param| {
allocator.free(param.name);
allocator.free(param.type_name);
}
allocator.free(func.params);
},
inline else => |*d| d.deinit(allocator),
}
}

View File

@ -1,4 +1,5 @@
const std = @import("std");
const io = @import("io");
const Allocator = std.mem.Allocator;
fn fixupZigName(name: []const u8) []u8 {
@ -26,12 +27,28 @@ pub const Declaration = union(enum) {
pub const OpaqueType = struct {
name: []const u8, // SDL_GPUDevice
doc_comment: ?[]const u8,
pub fn deinit(self: OpaqueType, allocator: Allocator) void {
allocator.free(self.name);
if (self.doc_comment) |doc| allocator.free(doc);
}
};
pub const EnumDecl = struct {
name: []const u8, // SDL_GPUPrimitiveType
values: []EnumValue,
doc_comment: ?[]const u8,
pub fn deinit(self: EnumDecl, allocator: Allocator) void {
allocator.free(self.name);
if (self.doc_comment) |doc| allocator.free(doc);
for (self.values) |val| {
allocator.free(val.name);
if (val.value) |v| allocator.free(v);
if (val.comment) |c| allocator.free(c);
}
allocator.free(self.values);
}
};
pub const EnumValue = struct {
@ -45,12 +62,34 @@ pub const StructDecl = struct {
fields: []FieldDecl,
doc_comment: ?[]const u8,
has_unions: bool = false, // If true, codegen should emit as opaque (C unions can't be represented in other languages)
pub fn deinit(self: StructDecl, allocator: Allocator) void {
allocator.free(self.name);
if (self.doc_comment) |doc| allocator.free(doc);
for (self.fields) |field| {
allocator.free(field.name);
allocator.free(field.type_name);
if (field.comment) |c| allocator.free(c);
}
allocator.free(self.fields);
}
};
pub const UnionDecl = struct {
name: []const u8, // SDL_Event
fields: []FieldDecl,
doc_comment: ?[]const u8,
pub fn deinit(self: UnionDecl, allocator: Allocator) void {
allocator.free(self.name);
if (self.doc_comment) |doc| allocator.free(doc);
for (self.fields) |field| {
allocator.free(field.name);
allocator.free(field.type_name);
if (field.comment) |c| allocator.free(c);
}
allocator.free(self.fields);
}
};
pub const FieldDecl = struct {
@ -64,6 +103,18 @@ pub const FlagDecl = struct {
underlying_type: []const u8, // Uint32
flags: []FlagValue,
doc_comment: ?[]const u8,
pub fn deinit(self: FlagDecl, allocator: Allocator) void {
allocator.free(self.name);
allocator.free(self.underlying_type);
if (self.doc_comment) |doc| allocator.free(doc);
for (self.flags) |flag| {
allocator.free(flag.name);
allocator.free(flag.value);
if (flag.comment) |c| allocator.free(c);
}
allocator.free(self.flags);
}
};
pub const FlagValue = struct {
@ -76,6 +127,12 @@ pub const TypedefDecl = struct {
name: []const u8, // SDL_PropertiesID
underlying_type: []const u8, // Uint32
doc_comment: ?[]const u8,
pub fn deinit(self: TypedefDecl, allocator: Allocator) void {
allocator.free(self.name);
allocator.free(self.underlying_type);
if (self.doc_comment) |doc| allocator.free(doc);
}
};
pub const FunctionPointerDecl = struct {
@ -83,6 +140,17 @@ pub const FunctionPointerDecl = struct {
return_type: []const u8, // Uint32
params: []ParamDecl,
doc_comment: ?[]const u8,
pub fn deinit(self: FunctionPointerDecl, allocator: Allocator) void {
allocator.free(self.name);
allocator.free(self.return_type);
if (self.doc_comment) |doc| allocator.free(doc);
for (self.params) |param| {
allocator.free(param.name);
allocator.free(param.type_name);
}
allocator.free(self.params);
}
};
/// C type alias - for function pointer typedefs that should alias to C type directly
@ -90,6 +158,11 @@ pub const FunctionPointerDecl = struct {
pub const CTypeAlias = struct {
name: []const u8, // SDL_HitTest
doc_comment: ?[]const u8,
pub fn deinit(self: CTypeAlias, allocator: Allocator) void {
allocator.free(self.name);
if (self.doc_comment) |doc| allocator.free(doc);
}
};
pub const FunctionDecl = struct {
@ -97,6 +170,17 @@ pub const FunctionDecl = struct {
return_type: []const u8, // SDL_GPUDevice *
params: []ParamDecl,
doc_comment: ?[]const u8,
pub fn deinit(self: FunctionDecl, allocator: Allocator) void {
allocator.free(self.name);
allocator.free(self.return_type);
if (self.doc_comment) |doc| allocator.free(doc);
for (self.params) |param| {
allocator.free(param.name);
allocator.free(param.type_name);
}
allocator.free(self.params);
}
};
pub const ParamDecl = struct {
@ -603,7 +687,7 @@ pub const Scanner = struct {
const body = try self.readBracedBlock();
defer self.allocator.free(body);
// Check if struct contains unions - C unions can't be represented in other languages
// Check if struct contains unions - C embedded unions can't be represented in other languages
const has_unions = std.mem.indexOf(u8, body, "union ") != null or
std.mem.indexOf(u8, body, "union{") != null or
std.mem.indexOf(u8, body, "union\n") != null or
@ -876,7 +960,7 @@ pub const Scanner = struct {
const type_parts = parts_list[0 .. parts_count - 1];
// Reconstruct type with array notation
var type_buf: [128]u8 = undefined;
var type_buf: [256]u8 = undefined;
var fbs = std.io.fixedBufferStream(&type_buf);
const writer = fbs.writer();
for (type_parts, 0..) |part, i| {
@ -889,6 +973,7 @@ pub const Scanner = struct {
return null;
};
}
writer.writeAll(bracket_part) catch {
if (comment) |c| self.allocator.free(c);
return null;
@ -1176,12 +1261,15 @@ pub const Scanner = struct {
"SDL_PRINTF_VARARG_FUNCV",
"SDL_WPRINTF_VARARG_FUNC",
"SDL_SCANF_VARARG_FUNC",
};
// these mean nothing in any other language than C, we just blow it away
const clang_acqrel = [_][]const u8{
"SDL_ACQUIRE",
"SDL_RELEASE",
};
for (vararg_macros) |macro| {
for (clang_acqrel) |macro| {
if (std.mem.indexOf(u8, text, macro)) |pos| {
// Find semicolon after this position
if (std.mem.indexOfScalarPos(u8, text, pos, ';')) |semi_pos| {
// Find the closing ) before semicolon
var paren_pos = semi_pos;
@ -1201,6 +1289,13 @@ pub const Scanner = struct {
}
}
for (vararg_macros) |macro| {
if (std.mem.indexOf(u8, text, macro)) |pos| {
_ = pos;
return null; // we dont attempt to generate vararg functions.
}
}
// Find SDLCALL to split return type and function name
const sdlcall_pos = std.mem.indexOf(u8, text, "SDLCALL ") orelse return null;
const return_type_str = std.mem.trim(u8, text[0..sdlcall_pos], " \t\n");
@ -1479,106 +1574,3 @@ pub const Scanner = struct {
return comment;
}
};
test "scan opaque typedef" {
const source = "typedef struct SDL_GPUDevice SDL_GPUDevice;";
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const allocator = arena.allocator();
var scanner = Scanner.init(allocator, source);
const decls = try scanner.scan();
try std.testing.expectEqual(@as(usize, 1), decls.len);
try std.testing.expect(decls[0] == .opaque_type);
try std.testing.expectEqualStrings("SDL_GPUDevice", decls[0].opaque_type.name);
}
test "scan function declaration" {
const source =
\\extern SDL_DECLSPEC bool SDLCALL SDL_GPUSupportsShaderFormats(
\\ SDL_GPUShaderFormat format_flags,
\\ const char *name);
;
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const allocator = arena.allocator();
var scanner = Scanner.init(allocator, source);
const decls = try scanner.scan();
try std.testing.expectEqual(@as(usize, 1), decls.len);
try std.testing.expect(decls[0] == .function_decl);
const func = decls[0].function_decl;
try std.testing.expectEqualStrings("SDL_GPUSupportsShaderFormats", func.name);
try std.testing.expectEqualStrings("bool", func.return_type);
}
test "scan flag typedef with newline before defines" {
const source =
\\typedef Uint32 SDL_GPUTextureUsageFlags;
\\
\\#define SDL_GPU_TEXTUREUSAGE_SAMPLER (1u << 0)
\\#define SDL_GPU_TEXTUREUSAGE_COLOR_TARGET (1u << 1)
\\#define SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET (1u << 2)
;
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const allocator = arena.allocator();
var scanner = Scanner.init(allocator, source);
const decls = try scanner.scan();
try std.testing.expectEqual(@as(usize, 1), decls.len);
try std.testing.expect(decls[0] == .flag_decl);
const flag = decls[0].flag_decl;
try std.testing.expectEqualStrings("SDL_GPUTextureUsageFlags", flag.name);
try std.testing.expectEqualStrings("Uint32", flag.underlying_type);
try std.testing.expectEqual(@as(usize, 3), flag.flags.len);
try std.testing.expectEqualStrings("SDL_GPU_TEXTUREUSAGE_SAMPLER", flag.flags[0].name);
try std.testing.expectEqualStrings("SDL_GPU_TEXTUREUSAGE_COLOR_TARGET", flag.flags[1].name);
try std.testing.expectEqualStrings("SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET", flag.flags[2].name);
}
test "scan flag typedef with multiple blank lines" {
const source =
\\typedef Uint32 SDL_GPUBufferUsageFlags;
\\
\\
\\#define SDL_GPU_BUFFERUSAGE_VERTEX (1u << 0)
\\#define SDL_GPU_BUFFERUSAGE_INDEX (1u << 1)
;
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const allocator = arena.allocator();
var scanner = Scanner.init(allocator, source);
const decls = try scanner.scan();
try std.testing.expectEqual(@as(usize, 1), decls.len);
try std.testing.expect(decls[0] == .flag_decl);
const flag = decls[0].flag_decl;
try std.testing.expectEqual(@as(usize, 2), flag.flags.len);
}
test "scan flag typedef with comments before defines" {
const source =
\\typedef Uint32 SDL_GPUColorComponentFlags;
\\
\\/* Comment here */
;
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const allocator = arena.allocator();
var scanner = Scanner.init(allocator, source);
const decls = try scanner.scan();
// Should still parse the typedef even if no #defines follow
try std.testing.expectEqual(@as(usize, 1), decls.len);
try std.testing.expect(decls[0] == .flag_decl);
const flag = decls[0].flag_decl;
try std.testing.expectEqualStrings("SDL_GPUColorComponentFlags", flag.name);
// No flags found, but that's ok
try std.testing.expectEqual(@as(usize, 0), flag.flags.len);
}

View File

@ -1,6 +1,8 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const io = @import("io");
var fmtBuffer: [256]u8 = undefined;
/// Convert C type to Zig type
/// Simple table-based conversion for SDL3 types
pub fn convertType(c_type: []const u8, allocator: Allocator) ![]const u8 {
@ -21,14 +23,34 @@ pub fn convertType(c_type: []const u8, allocator: Allocator) ![]const u8 {
// Handle array types: "Uint8[2]" -> "[2]u8"
if (std.mem.indexOf(u8, trimmed, "[")) |bracket_pos| {
const base_type = std.mem.trim(u8, trimmed[0..bracket_pos], " \t");
const array_part = trimmed[bracket_pos..]; // "[2]"
var array_part = trimmed[bracket_pos..]; // "[2]"
// Recursively convert the base type
const zig_base = try convertType(base_type, allocator);
defer allocator.free(zig_base);
const inner = array_part[1 .. array_part.len - 1];
if (inner.len > 0) {
var alias_to_c: bool = false;
_ = std.fmt.parseInt(u32, inner, 0) catch {
// not a real error
alias_to_c = true;
};
if (alias_to_c) {
var b = std.io.fixedBufferStream(&fmtBuffer);
_ = try b.write("[");
_ = try b.write("c.");
_ = try b.write(inner);
_ = try b.write("]");
array_part = b.getWritten();
std.debug.print("arrya_part = {s}\n", .{array_part});
}
}
// Return Zig array notation: [size]Type
return try std.fmt.allocPrint(allocator, "{s}{s}", .{array_part, zig_base});
return try std.fmt.allocPrint(allocator, "{s}{s}", .{ array_part, zig_base });
}
// Primitives
@ -86,7 +108,7 @@ pub fn convertType(c_type: []const u8, allocator: Allocator) ![]const u8 {
return std.fmt.allocPrint(allocator, "[*c][*c]{s}", .{base_type});
}
}
// Handle primitive pointer types
if (std.mem.eql(u8, trimmed, "int *")) return try allocator.dupe(u8, "*c_int");
if (std.mem.eql(u8, trimmed, "bool *")) return try allocator.dupe(u8, "*bool");
@ -102,7 +124,7 @@ pub fn convertType(c_type: []const u8, allocator: Allocator) ![]const u8 {
if (std.mem.eql(u8, trimmed, "Sint16 *")) return try allocator.dupe(u8, "*i16");
if (std.mem.eql(u8, trimmed, "Sint32 *")) return try allocator.dupe(u8, "*i32");
if (std.mem.eql(u8, trimmed, "const bool *")) return try allocator.dupe(u8, "*const bool");
if (std.mem.startsWith(u8, trimmed, "const ")) {
const rest = trimmed[6..];
if (std.mem.endsWith(u8, rest, " *") or std.mem.endsWith(u8, rest, "*")) {
@ -144,7 +166,7 @@ pub fn convertType(c_type: []const u8, allocator: Allocator) ![]const u8 {
return std.fmt.allocPrint(allocator, "*const {s}", .{struct_name});
}
}
// Handle "Foo *" for any remaining types (fallback to C pointer)
if (std.mem.endsWith(u8, trimmed, " *")) {
const base_type = trimmed[0 .. trimmed.len - 2];
@ -190,26 +212,26 @@ pub fn getCastType(zig_type: []const u8) CastType {
/// Example: Sint64 (SDLCALL *size)(void *userdata) -> *const fn (?*anyopaque) callconv(.C) i64
fn convertFunctionPointerType(c_type: []const u8, allocator: Allocator) ![]const u8 {
// Pattern: ReturnType (SDLCALL *name)(params) or ReturnType (*name)(params)
// Find the return type (everything before the opening paren)
const open_paren = std.mem.indexOf(u8, c_type, "(") orelse return try allocator.dupe(u8, c_type);
const return_type_str = std.mem.trim(u8, c_type[0..open_paren], " \t");
// Find the parameters (between the last ( and the last ))
const last_open_paren = std.mem.lastIndexOf(u8, c_type, "(") orelse return try allocator.dupe(u8, c_type);
const last_close_paren = std.mem.lastIndexOf(u8, c_type, ")") orelse return try allocator.dupe(u8, c_type);
if (last_close_paren <= last_open_paren) return try allocator.dupe(u8, c_type);
const params_str = std.mem.trim(u8, c_type[last_open_paren + 1 .. last_close_paren], " \t");
// Convert return type (but don't recursively convert function pointers)
const zig_return = if (std.mem.indexOf(u8, return_type_str, "(") != null)
try allocator.dupe(u8, return_type_str)
else
try convertType(return_type_str, allocator);
defer allocator.free(zig_return);
// Convert parameters
var params_list = std.ArrayList([]const u8).init(allocator);
defer {
@ -218,18 +240,18 @@ fn convertFunctionPointerType(c_type: []const u8, allocator: Allocator) ![]const
}
params_list.deinit();
}
// Parse comma-separated parameters
var param_iter = std.mem.splitScalar(u8, params_str, ',');
while (param_iter.next()) |param| {
const trimmed_param = std.mem.trim(u8, param, " \t");
if (trimmed_param.len == 0) continue;
// Extract just the type (remove parameter name if present)
// Pattern: "void *userdata" -> "void *"
// Pattern: "Sint64 offset" -> "Sint64"
var param_type: []const u8 = trimmed_param;
// Find the last space that separates type from name
if (std.mem.lastIndexOf(u8, trimmed_param, " ")) |space_pos| {
// Check if what comes after is an identifier (not a *)
@ -238,7 +260,7 @@ fn convertFunctionPointerType(c_type: []const u8, allocator: Allocator) ![]const
param_type = std.mem.trimRight(u8, trimmed_param[0..space_pos], " \t");
}
}
// Don't recursively convert function pointers in params
const zig_param = if (std.mem.indexOf(u8, param_type, "(") != null)
try allocator.dupe(u8, param_type)
@ -246,21 +268,21 @@ fn convertFunctionPointerType(c_type: []const u8, allocator: Allocator) ![]const
try convertType(param_type, allocator);
try params_list.append(zig_param);
}
// Build Zig function pointer type
var result = std.ArrayList(u8).init(allocator);
defer result.deinit();
try result.appendSlice("?*const fn (");
for (params_list.items, 0..) |param, i| {
if (i > 0) try result.appendSlice(", ");
try result.appendSlice(param);
}
try result.appendSlice(") callconv(.C) ");
try result.appendSlice(zig_return);
return try result.toOwnedSlice();
}
@ -271,41 +293,3 @@ pub const CastType = enum {
int_from_enum,
enum_from_int,
};
test "convert primitive types" {
const t1 = try convertType("float", std.testing.allocator);
defer std.testing.allocator.free(t1);
try std.testing.expectEqualStrings("f32", t1);
const t2 = try convertType("Uint32", std.testing.allocator);
defer std.testing.allocator.free(t2);
try std.testing.expectEqualStrings("u32", t2);
const t3 = try convertType("bool", std.testing.allocator);
defer std.testing.allocator.free(t3);
try std.testing.expectEqualStrings("bool", t3);
}
test "convert SDL types" {
const t1 = try convertType("SDL_GPUDevice", std.testing.allocator);
defer std.testing.allocator.free(t1);
try std.testing.expectEqualStrings("GPUDevice", t1);
const t2 = try convertType("SDL_GPUDevice *", std.testing.allocator);
defer std.testing.allocator.free(t2);
try std.testing.expectEqualStrings("?*GPUDevice", t2);
const t3 = try convertType("const SDL_GPUViewport *", std.testing.allocator);
defer std.testing.allocator.free(t3);
try std.testing.expectEqualStrings("*const GPUViewport", t3);
}
test "convert pointer types" {
const t1 = try convertType("const char *", std.testing.allocator);
defer std.testing.allocator.free(t1);
try std.testing.expectEqualStrings("[*c]const u8", t1);
const t2 = try convertType("void *", std.testing.allocator);
defer std.testing.allocator.free(t2);
try std.testing.expectEqualStrings("?*anyopaque", t2);
}

View File

@ -1,16 +1,6 @@
const std = @import("std");
pub const c = @import("c.zig").c;
pub inline fn setError(fmt: [*c]const u8, ...) bool {
return c.SDL_SetError(
fmt,
);
}
pub inline fn setErrorV(fmt: [*c]const u8, ap: std.builtin.VaList) bool {
return c.SDL_SetErrorV(fmt, ap);
}
pub inline fn outOfMemory() bool {
return c.SDL_OutOfMemory();
}

View File

@ -1039,15 +1039,6 @@ pub const Renderer = opaque {
pub inline fn renderDebugText(renderer: *Renderer, x: f32, y: f32, str: [*c]const u8) bool {
return c.SDL_RenderDebugText(renderer, x, y, str);
}
pub inline fn renderDebugTextFormat(renderer: *Renderer, x: f32, y: f32, fmt: [*c]const u8, ...) bool {
return c.SDL_RenderDebugTextFormat(
renderer,
x,
y,
fmt,
);
}
};
pub const Texture = opaque {