migration to generated gpu sdl3 bindings and sdl 3.2.30
This commit is contained in:
parent
44e7928c6d
commit
45ee35163c
70
build.zig
70
build.zig
|
|
@ -155,6 +155,41 @@ const MakeDirStep = struct {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const WriteStaticFileStep = struct {
|
||||||
|
step: std.Build.Step,
|
||||||
|
path: []const u8,
|
||||||
|
contents: []const u8,
|
||||||
|
|
||||||
|
pub fn create(b: *std.Build, path: []const u8, contents: []const u8) *std.Build.Step {
|
||||||
|
const self = b.allocator.create(WriteStaticFileStep) catch @panic("OOM");
|
||||||
|
self.* = .{
|
||||||
|
.step = std.Build.Step.init(.{
|
||||||
|
.id = .custom,
|
||||||
|
.name = b.fmt("write {s}", .{path}),
|
||||||
|
.owner = b,
|
||||||
|
.makeFn = make,
|
||||||
|
}),
|
||||||
|
.path = b.dupe(path),
|
||||||
|
.contents = b.dupe(contents),
|
||||||
|
};
|
||||||
|
return &self.step;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make(step: *std.Build.Step, options: std.Build.Step.MakeOptions) !void {
|
||||||
|
_ = options;
|
||||||
|
const self: *WriteStaticFileStep = @fieldParentPtr("step", step);
|
||||||
|
|
||||||
|
if (std.fs.path.dirname(self.path)) |dir| {
|
||||||
|
try std.fs.cwd().makePath(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
try std.fs.cwd().writeFile(.{
|
||||||
|
.sub_path = self.path,
|
||||||
|
.data = self.contents,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
pub fn generateApi(b: *std.Build, parser_exe: *std.Build.Step.Compile, fetch_sdl_step: *std.Build.Step) void {
|
pub fn generateApi(b: *std.Build, parser_exe: *std.Build.Step.Compile, fetch_sdl_step: *std.Build.Step) void {
|
||||||
const source_dir = b.option([]const u8, "sourceDir", "Parse SDL headers from an existing local directory instead of fetching git");
|
const source_dir = b.option([]const u8, "sourceDir", "Parse SDL headers from an existing local directory instead of fetching git");
|
||||||
const output_dir = b.option([]const u8, "outputDir", "Directory where generated api/ and json/ folders should be written");
|
const output_dir = b.option([]const u8, "outputDir", "Directory where generated api/ and json/ folders should be written");
|
||||||
|
|
@ -190,8 +225,30 @@ pub fn generateApi(b: *std.Build, parser_exe: *std.Build.Step.Compile, fetch_sdl
|
||||||
path_prep_step = basedir_step;
|
path_prep_step = basedir_step;
|
||||||
}
|
}
|
||||||
|
|
||||||
// All public SDL3 API headers (53 total)
|
const output_root = if (effective_output_dir) |dir| b.fmt("{s}/api", .{dir}) else "api";
|
||||||
// Skipped: assert, thread, hidapi, mutex, tray (not core APIs or problematic)
|
const json_root = if (effective_output_dir) |dir| b.fmt("{s}/json", .{dir}) else "json";
|
||||||
|
const effective_c_import_path = c_import_path orelse "c.zig";
|
||||||
|
const c_bridge_target = if (c_import_path) |path| path else "../c.zig";
|
||||||
|
const c_bridge_step = WriteStaticFileStep.create(
|
||||||
|
b,
|
||||||
|
b.fmt("{s}/c.zig", .{output_root}),
|
||||||
|
b.fmt("pub const c = @import(\"{s}\").c;\n", .{c_bridge_target}),
|
||||||
|
);
|
||||||
|
c_bridge_step.dependOn(path_prep_step);
|
||||||
|
path_prep_step = c_bridge_step;
|
||||||
|
|
||||||
|
const stdinc_bridge_step = WriteStaticFileStep.create(
|
||||||
|
b,
|
||||||
|
b.fmt("{s}/stdinc.zig", .{output_root}),
|
||||||
|
\\pub const c = @import("c.zig").c;
|
||||||
|
\\pub const Time = i64;
|
||||||
|
\\pub const FunctionPointer = c.SDL_FunctionPointer;
|
||||||
|
\\
|
||||||
|
);
|
||||||
|
stdinc_bridge_step.dependOn(path_prep_step);
|
||||||
|
path_prep_step = stdinc_bridge_step;
|
||||||
|
|
||||||
|
// All public SDL3 API headers
|
||||||
const headers_to_generate = [_]struct { header: []const u8, output: []const u8 }{
|
const headers_to_generate = [_]struct { header: []const u8, output: []const u8 }{
|
||||||
// .{ .header = "SDL_asyncio.h", .output = "asyncio" },
|
// .{ .header = "SDL_asyncio.h", .output = "asyncio" },
|
||||||
// .{ .header = "SDL_atomic.h", .output = "atomic" },
|
// .{ .header = "SDL_atomic.h", .output = "atomic" },
|
||||||
|
|
@ -212,7 +269,7 @@ pub fn generateApi(b: *std.Build, parser_exe: *std.Build.Step.Compile, fetch_sdl
|
||||||
// .{ .header = "SDL_hidapi.h", .output = "hidapi" }, // Skipped: not core API
|
// .{ .header = "SDL_hidapi.h", .output = "hidapi" }, // Skipped: not core API
|
||||||
.{ .header = "SDL_hints.h", .output = "hints" },
|
.{ .header = "SDL_hints.h", .output = "hints" },
|
||||||
.{ .header = "SDL_init.h", .output = "init" },
|
.{ .header = "SDL_init.h", .output = "init" },
|
||||||
// .{ .header = "SDL_iostream.h", .output = "iostream" }, // Skipped: complex I/O API
|
.{ .header = "SDL_iostream.h", .output = "iostream" },
|
||||||
.{ .header = "SDL_joystick.h", .output = "joystick" },
|
.{ .header = "SDL_joystick.h", .output = "joystick" },
|
||||||
.{ .header = "SDL_keyboard.h", .output = "keyboard" },
|
.{ .header = "SDL_keyboard.h", .output = "keyboard" },
|
||||||
.{ .header = "SDL_keycode.h", .output = "keycode" },
|
.{ .header = "SDL_keycode.h", .output = "keycode" },
|
||||||
|
|
@ -252,9 +309,6 @@ pub fn generateApi(b: *std.Build, parser_exe: *std.Build.Step.Compile, fetch_sdl
|
||||||
b.fmt("{s}/{s}", .{ dir, header_root_suffix.? })
|
b.fmt("{s}/{s}", .{ dir, header_root_suffix.? })
|
||||||
else
|
else
|
||||||
"sdl3/include/SDL3";
|
"sdl3/include/SDL3";
|
||||||
const output_root = if (effective_output_dir) |dir| b.fmt("{s}/api", .{dir}) else "api";
|
|
||||||
const json_root = if (effective_output_dir) |dir| b.fmt("{s}/json", .{dir}) else "json";
|
|
||||||
|
|
||||||
const timestamp_arg = b.fmt("--timestamp={d}", .{timestamp});
|
const timestamp_arg = b.fmt("--timestamp={d}", .{timestamp});
|
||||||
|
|
||||||
for (headers_to_generate) |header_info| {
|
for (headers_to_generate) |header_info| {
|
||||||
|
|
@ -265,9 +319,7 @@ pub fn generateApi(b: *std.Build, parser_exe: *std.Build.Step.Compile, fetch_sdl
|
||||||
if (basedir) |dir| {
|
if (basedir) |dir| {
|
||||||
regenerate.addArg(b.fmt("--basedir={s}", .{dir}));
|
regenerate.addArg(b.fmt("--basedir={s}", .{dir}));
|
||||||
}
|
}
|
||||||
if (c_import_path) |path| {
|
regenerate.addArg(b.fmt("--c-import={s}", .{effective_c_import_path}));
|
||||||
regenerate.addArg(b.fmt("--c-import={s}", .{path}));
|
|
||||||
}
|
|
||||||
// regenerate.addArg(b.fmt("--output=api/{s}.zig --mocks=mocks/{s}.c", .{ header_info.output, header_info.output }));
|
// regenerate.addArg(b.fmt("--output=api/{s}.zig --mocks=mocks/{s}.c", .{ header_info.output, header_info.output }));
|
||||||
regenerate.step.dependOn(path_prep_step);
|
regenerate.step.dependOn(path_prep_step);
|
||||||
regenerate_step.dependOn(®enerate.step);
|
regenerate_step.dependOn(®enerate.step);
|
||||||
|
|
|
||||||
233
src/codegen.zig
233
src/codegen.zig
|
|
@ -26,12 +26,14 @@ pub const CodeGenOptions = struct {
|
||||||
decls: []const ResolvedDecl,
|
decls: []const ResolvedDecl,
|
||||||
module_name: []const u8,
|
module_name: []const u8,
|
||||||
c_import_path: []const u8 = "c.zig",
|
c_import_path: []const u8 = "c.zig",
|
||||||
|
skipped: []const patterns.SkippedDecl = &.{},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const CodeGen = struct {
|
pub const CodeGen = struct {
|
||||||
decls: []const ResolvedDecl,
|
decls: []const ResolvedDecl,
|
||||||
module_name: []const u8,
|
module_name: []const u8,
|
||||||
c_import_path: []const u8,
|
c_import_path: []const u8,
|
||||||
|
skipped: []const patterns.SkippedDecl,
|
||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
output: std.ArrayList(u8),
|
output: std.ArrayList(u8),
|
||||||
opaque_methods: std.StringHashMap(std.ArrayList(patterns.FunctionDecl)),
|
opaque_methods: std.StringHashMap(std.ArrayList(patterns.FunctionDecl)),
|
||||||
|
|
@ -43,6 +45,7 @@ pub const CodeGen = struct {
|
||||||
.decls = options.decls,
|
.decls = options.decls,
|
||||||
.module_name = options.module_name,
|
.module_name = options.module_name,
|
||||||
.c_import_path = options.c_import_path,
|
.c_import_path = options.c_import_path,
|
||||||
|
.skipped = options.skipped,
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.output = try std.ArrayList(u8).initCapacity(allocator, 4096),
|
.output = try std.ArrayList(u8).initCapacity(allocator, 4096),
|
||||||
.opaque_methods = std.StringHashMap(std.ArrayList(patterns.FunctionDecl)).init(allocator),
|
.opaque_methods = std.StringHashMap(std.ArrayList(patterns.FunctionDecl)).init(allocator),
|
||||||
|
|
@ -200,6 +203,14 @@ pub const CodeGen = struct {
|
||||||
if (self.aliases.items.len > 0) {
|
if (self.aliases.items.len > 0) {
|
||||||
try self.output.appendSlice(self.allocator, "\n");
|
try self.output.appendSlice(self.allocator, "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (self.skipped.len > 0) {
|
||||||
|
try self.output.appendSlice(self.allocator, "pub const unsupported = struct {\n");
|
||||||
|
for (self.skipped) |item| {
|
||||||
|
try self.output.writer(self.allocator).print(" pub const @\"{s}\" = \"{s}\";\n", .{ item.name, item.reason });
|
||||||
|
}
|
||||||
|
try self.output.appendSlice(self.allocator, "};\n\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn writeDeclarations(self: *CodeGen) !void {
|
fn writeDeclarations(self: *CodeGen) !void {
|
||||||
|
|
@ -446,7 +457,7 @@ pub const CodeGen = struct {
|
||||||
|
|
||||||
try self.output.writer(self.allocator).print("pub const {s} = extern struct {{\n", .{zig_name});
|
try self.output.writer(self.allocator).print("pub const {s} = extern struct {{\n", .{zig_name});
|
||||||
for (struct_decl.fields) |field| {
|
for (struct_decl.fields) |field| {
|
||||||
const zig_type = try types.convertType(field.type_name, self.allocator);
|
const zig_type = try self.convertAggregateFieldType(field.type_name);
|
||||||
defer self.allocator.free(zig_type);
|
defer self.allocator.free(zig_type);
|
||||||
if (field.comment) |comment| {
|
if (field.comment) |comment| {
|
||||||
try self.output.writer(self.allocator).print(" {s}: {s}, // {s}\n", .{ field.name, zig_type, comment });
|
try self.output.writer(self.allocator).print(" {s}: {s}, // {s}\n", .{ field.name, zig_type, comment });
|
||||||
|
|
@ -462,7 +473,7 @@ pub const CodeGen = struct {
|
||||||
if (union_decl.doc_comment) |doc| try self.writeDocComment(doc);
|
if (union_decl.doc_comment) |doc| try self.writeDocComment(doc);
|
||||||
try self.output.writer(self.allocator).print("pub const {s} = extern union {{\n", .{zig_name});
|
try self.output.writer(self.allocator).print("pub const {s} = extern union {{\n", .{zig_name});
|
||||||
for (union_decl.fields) |field| {
|
for (union_decl.fields) |field| {
|
||||||
const zig_type = try types.convertType(field.type_name, self.allocator);
|
const zig_type = try self.convertAggregateFieldType(field.type_name);
|
||||||
defer self.allocator.free(zig_type);
|
defer self.allocator.free(zig_type);
|
||||||
if (field.comment) |comment| {
|
if (field.comment) |comment| {
|
||||||
try self.output.writer(self.allocator).print(" {s}: {s}, // {s}\n", .{ field.name, zig_type, comment });
|
try self.output.writer(self.allocator).print(" {s}: {s}, // {s}\n", .{ field.name, zig_type, comment });
|
||||||
|
|
@ -550,7 +561,7 @@ pub const CodeGen = struct {
|
||||||
|
|
||||||
try self.output.writer(self.allocator).print(" pub inline fn {s}(", .{zig_name});
|
try self.output.writer(self.allocator).print(" pub inline fn {s}(", .{zig_name});
|
||||||
for (func.params, 0..) |param, i| {
|
for (func.params, 0..) |param, i| {
|
||||||
const zig_type = try types.convertType(param.type_name, self.allocator);
|
const zig_type = try self.convertFunctionParamType(func, param);
|
||||||
defer self.allocator.free(zig_type);
|
defer self.allocator.free(zig_type);
|
||||||
if (i > 0) try self.output.appendSlice(self.allocator, ", ");
|
if (i > 0) try self.output.appendSlice(self.allocator, ", ");
|
||||||
|
|
||||||
|
|
@ -568,7 +579,7 @@ pub const CodeGen = struct {
|
||||||
try self.output.writer(self.allocator).print(") {s} {{\n", .{zig_return_type});
|
try self.output.writer(self.allocator).print(") {s} {{\n", .{zig_return_type});
|
||||||
try self.output.appendSlice(self.allocator, " return ");
|
try self.output.appendSlice(self.allocator, " return ");
|
||||||
|
|
||||||
const return_cast = if (!std.mem.eql(u8, zig_return_type, "void")) types.getCastType(zig_return_type) else .none;
|
const return_cast = if (!std.mem.eql(u8, zig_return_type, "void")) types.getReturnCastType(zig_return_type) else .none;
|
||||||
if (return_cast != .none) {
|
if (return_cast != .none) {
|
||||||
try self.output.writer(self.allocator).print("{s}(", .{castTypeToString(return_cast)});
|
try self.output.writer(self.allocator).print("{s}(", .{castTypeToString(return_cast)});
|
||||||
}
|
}
|
||||||
|
|
@ -584,9 +595,9 @@ pub const CodeGen = struct {
|
||||||
} else try self.allocator.dupe(u8, param.name);
|
} else try self.allocator.dupe(u8, param.name);
|
||||||
defer self.allocator.free(param_name);
|
defer self.allocator.free(param_name);
|
||||||
|
|
||||||
const zig_param_type = try types.convertType(param.type_name, self.allocator);
|
const zig_param_type = try self.convertFunctionParamType(func, param);
|
||||||
defer self.allocator.free(zig_param_type);
|
defer self.allocator.free(zig_param_type);
|
||||||
const param_cast = types.getCastType(zig_param_type);
|
const param_cast = self.getFunctionParamCastType(param, zig_param_type);
|
||||||
|
|
||||||
if (param_cast == .none) {
|
if (param_cast == .none) {
|
||||||
try self.output.writer(self.allocator).print("{s}", .{param_name});
|
try self.output.writer(self.allocator).print("{s}", .{param_name});
|
||||||
|
|
@ -614,7 +625,7 @@ pub const CodeGen = struct {
|
||||||
try self.output.writer(self.allocator).print("pub inline fn {s}(", .{zig_name});
|
try self.output.writer(self.allocator).print("pub inline fn {s}(", .{zig_name});
|
||||||
|
|
||||||
for (func.params, 0..) |param, i| {
|
for (func.params, 0..) |param, i| {
|
||||||
const zig_type = try types.convertType(param.type_name, self.allocator);
|
const zig_type = try self.convertFunctionParamType(func, param);
|
||||||
defer self.allocator.free(zig_type);
|
defer self.allocator.free(zig_type);
|
||||||
if (i > 0) try self.output.appendSlice(self.allocator, ", ");
|
if (i > 0) try self.output.appendSlice(self.allocator, ", ");
|
||||||
if (param.name.len > 0) {
|
if (param.name.len > 0) {
|
||||||
|
|
@ -626,7 +637,7 @@ pub const CodeGen = struct {
|
||||||
try self.output.writer(self.allocator).print(") {s} {{\n", .{zig_return_type});
|
try self.output.writer(self.allocator).print(") {s} {{\n", .{zig_return_type});
|
||||||
try self.output.appendSlice(self.allocator, " return ");
|
try self.output.appendSlice(self.allocator, " return ");
|
||||||
|
|
||||||
const return_cast = if (!std.mem.eql(u8, zig_return_type, "void")) types.getCastType(zig_return_type) else .none;
|
const return_cast = if (!std.mem.eql(u8, zig_return_type, "void")) types.getReturnCastType(zig_return_type) else .none;
|
||||||
if (return_cast != .none) {
|
if (return_cast != .none) {
|
||||||
try self.output.writer(self.allocator).print("{s}(", .{castTypeToString(return_cast)});
|
try self.output.writer(self.allocator).print("{s}(", .{castTypeToString(return_cast)});
|
||||||
}
|
}
|
||||||
|
|
@ -635,9 +646,9 @@ pub const CodeGen = struct {
|
||||||
for (func.params, 0..) |param, i| {
|
for (func.params, 0..) |param, i| {
|
||||||
if (i > 0) try self.output.appendSlice(self.allocator, ", ");
|
if (i > 0) try self.output.appendSlice(self.allocator, ", ");
|
||||||
if (param.name.len > 0) {
|
if (param.name.len > 0) {
|
||||||
const zig_param_type = try types.convertType(param.type_name, self.allocator);
|
const zig_param_type = try self.convertFunctionParamType(func, param);
|
||||||
defer self.allocator.free(zig_param_type);
|
defer self.allocator.free(zig_param_type);
|
||||||
const param_cast = types.getCastType(zig_param_type);
|
const param_cast = self.getFunctionParamCastType(param, zig_param_type);
|
||||||
|
|
||||||
if (param_cast == .none) {
|
if (param_cast == .none) {
|
||||||
try self.output.writer(self.allocator).print("{s}", .{param.name});
|
try self.output.writer(self.allocator).print("{s}", .{param.name});
|
||||||
|
|
@ -655,6 +666,118 @@ pub const CodeGen = struct {
|
||||||
try self.output.appendSlice(self.allocator, "}\n\n");
|
try self.output.appendSlice(self.allocator, "}\n\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn convertAggregateFieldType(self: *CodeGen, c_type: []const u8) ![]const u8 {
|
||||||
|
if (self.isOpaqueFieldPointerType(c_type)) {
|
||||||
|
return try types.convertType(c_type, self.allocator);
|
||||||
|
}
|
||||||
|
return try types.convertStructFieldType(c_type, self.allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convertFunctionParamType(self: *CodeGen, func: patterns.FunctionDecl, param: patterns.ParamDecl) ![]const u8 {
|
||||||
|
const zig_type = try types.convertType(param.type_name, self.allocator);
|
||||||
|
errdefer self.allocator.free(zig_type);
|
||||||
|
|
||||||
|
if (std.mem.startsWith(u8, zig_type, "?*") or std.mem.startsWith(u8, zig_type, "[*c]")) {
|
||||||
|
return zig_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
const should_make_nullable =
|
||||||
|
self.paramMayBeNull(func, param.name) or
|
||||||
|
(std.mem.startsWith(u8, zig_type, "*") and !std.mem.startsWith(u8, zig_type, "*const "));
|
||||||
|
|
||||||
|
if (should_make_nullable and std.mem.startsWith(u8, zig_type, "*")) {
|
||||||
|
const nullable = try std.fmt.allocPrint(self.allocator, "?{s}", .{zig_type});
|
||||||
|
self.allocator.free(zig_type);
|
||||||
|
return nullable;
|
||||||
|
}
|
||||||
|
|
||||||
|
return zig_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getFunctionParamCastType(self: *CodeGen, param: patterns.ParamDecl, zig_param_type: []const u8) types.CastType {
|
||||||
|
if (self.isSDLPointerArrayParam(param.type_name)) {
|
||||||
|
return .ptr_cast;
|
||||||
|
}
|
||||||
|
return types.getParamCastType(zig_param_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paramMayBeNull(self: *CodeGen, func: patterns.FunctionDecl, param_name: []const u8) bool {
|
||||||
|
_ = self;
|
||||||
|
if (param_name.len == 0) return false;
|
||||||
|
const doc = func.doc_comment orelse return false;
|
||||||
|
|
||||||
|
const marker = std.fmt.allocPrint(std.heap.smp_allocator, "\\param {s}", .{param_name}) catch return false;
|
||||||
|
defer std.heap.smp_allocator.free(marker);
|
||||||
|
|
||||||
|
const start = std.mem.indexOf(u8, doc, marker) orelse return false;
|
||||||
|
const tail = doc[start + marker.len ..];
|
||||||
|
const next_param = std.mem.indexOf(u8, tail, "\\param ") orelse tail.len;
|
||||||
|
const next_returns = std.mem.indexOf(u8, tail, "\\returns") orelse tail.len;
|
||||||
|
const end = @min(next_param, next_returns);
|
||||||
|
const param_doc = tail[0..end];
|
||||||
|
|
||||||
|
return std.mem.indexOf(u8, param_doc, "may be NULL") != null or
|
||||||
|
std.mem.indexOf(u8, param_doc, "or NULL") != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn isOpaqueFieldPointerType(self: *CodeGen, c_type: []const u8) bool {
|
||||||
|
const trimmed = std.mem.trim(u8, c_type, " \t");
|
||||||
|
if (std.mem.indexOf(u8, trimmed, "(SDLCALL *") != null or std.mem.indexOf(u8, trimmed, "(*") != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (std.mem.indexOfScalar(u8, trimmed, '*') == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var rest = trimmed;
|
||||||
|
if (std.mem.startsWith(u8, rest, "const ")) {
|
||||||
|
rest = std.mem.trim(u8, rest[6..], " \t");
|
||||||
|
}
|
||||||
|
|
||||||
|
const first_star = std.mem.indexOfScalar(u8, rest, '*') orelse return false;
|
||||||
|
const base_type = std.mem.trim(u8, rest[0..first_star], " \t");
|
||||||
|
if (!std.mem.startsWith(u8, base_type, "SDL_")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const zig_name = naming.typeNameToZig(base_type);
|
||||||
|
if (self.opaque_methods.contains(zig_name)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (self.decls) |resolved| {
|
||||||
|
if (resolved.decl != .opaque_type) continue;
|
||||||
|
if (std.mem.eql(u8, resolved.decl.opaque_type.name, base_type)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (std.mem.eql(u8, naming.typeNameToZig(resolved.decl.opaque_type.name), zig_name)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn isSDLPointerArrayParam(self: *CodeGen, c_type: []const u8) bool {
|
||||||
|
_ = self;
|
||||||
|
const trimmed = std.mem.trim(u8, c_type, " \t");
|
||||||
|
if (std.mem.indexOf(u8, trimmed, "(SDLCALL *") != null or std.mem.indexOf(u8, trimmed, "(*") != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (std.mem.count(u8, trimmed, "*") < 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var rest = trimmed;
|
||||||
|
if (std.mem.startsWith(u8, rest, "const ")) {
|
||||||
|
rest = std.mem.trim(u8, rest[6..], " \t");
|
||||||
|
}
|
||||||
|
|
||||||
|
const first_star = std.mem.indexOfScalar(u8, rest, '*') orelse return false;
|
||||||
|
const base_type = std.mem.trim(u8, rest[0..first_star], " \t");
|
||||||
|
return std.mem.startsWith(u8, base_type, "SDL_");
|
||||||
|
}
|
||||||
|
|
||||||
fn castTypeToString(cast_type: types.CastType) []const u8 {
|
fn castTypeToString(cast_type: types.CastType) []const u8 {
|
||||||
return switch (cast_type) {
|
return switch (cast_type) {
|
||||||
.none => "none",
|
.none => "none",
|
||||||
|
|
@ -775,3 +898,93 @@ test "primary declarations are emitted without self imports" {
|
||||||
try std.testing.expect(std.mem.indexOf(u8, output, "const video_api = @import(\"video.zig\");") == null);
|
try std.testing.expect(std.mem.indexOf(u8, output, "const video_api = @import(\"video.zig\");") == null);
|
||||||
try std.testing.expect(std.mem.indexOf(u8, output, "pub const Window = opaque") != null);
|
try std.testing.expect(std.mem.indexOf(u8, output, "pub const Window = opaque") != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "function params make plain out pointers nullable" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
var gen = CodeGen{
|
||||||
|
.decls = &.{},
|
||||||
|
.module_name = "gpu",
|
||||||
|
.c_import_path = "c.zig",
|
||||||
|
.skipped = &.{},
|
||||||
|
.allocator = allocator,
|
||||||
|
.output = .empty,
|
||||||
|
.opaque_methods = std.StringHashMap(std.ArrayList(patterns.FunctionDecl)).init(allocator),
|
||||||
|
.import_modules = .empty,
|
||||||
|
.aliases = .empty,
|
||||||
|
};
|
||||||
|
defer gen.deinit();
|
||||||
|
|
||||||
|
var params = [_]patterns.ParamDecl{
|
||||||
|
.{ .name = "swapchain_texture_width", .type_name = "Uint32 *" },
|
||||||
|
};
|
||||||
|
const func = patterns.FunctionDecl{
|
||||||
|
.name = "SDL_WaitAndAcquireGPUSwapchainTexture",
|
||||||
|
.return_type = "bool",
|
||||||
|
.params = params[0..],
|
||||||
|
.doc_comment = null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const zig_type = try gen.convertFunctionParamType(func, params[0]);
|
||||||
|
defer allocator.free(zig_type);
|
||||||
|
try std.testing.expectEqualStrings("?*u32", zig_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "opaque methods cast enum params to C ABI" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
|
||||||
|
var decls: std.ArrayList(ResolvedDecl) = .empty;
|
||||||
|
defer {
|
||||||
|
for (decls.items) |decl| decl.deinit(allocator);
|
||||||
|
decls.deinit(allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
try decls.append(allocator, .{
|
||||||
|
.decl = .{ .opaque_type = .{ .name = try allocator.dupe(u8, "SDL_GPURenderPass"), .doc_comment = null } },
|
||||||
|
.source_header = try allocator.dupe(u8, "SDL_gpu.h"),
|
||||||
|
.is_primary_header_decl = true,
|
||||||
|
});
|
||||||
|
try decls.append(allocator, .{
|
||||||
|
.decl = .{ .struct_decl = .{
|
||||||
|
.name = try allocator.dupe(u8, "SDL_GPUBufferBinding"),
|
||||||
|
.fields = &.{},
|
||||||
|
.doc_comment = null,
|
||||||
|
.has_unions = false,
|
||||||
|
} },
|
||||||
|
.source_header = try allocator.dupe(u8, "SDL_gpu.h"),
|
||||||
|
.is_primary_header_decl = true,
|
||||||
|
});
|
||||||
|
const enum_values = try allocator.alloc(patterns.EnumValue, 1);
|
||||||
|
enum_values[0] = .{ .name = try allocator.dupe(u8, "SDL_GPU_INDEXELEMENTSIZE_16BIT"), .value = null, .comment = null };
|
||||||
|
try decls.append(allocator, .{
|
||||||
|
.decl = .{ .enum_decl = .{
|
||||||
|
.name = try allocator.dupe(u8, "SDL_GPUIndexElementSize"),
|
||||||
|
.values = enum_values,
|
||||||
|
.doc_comment = null,
|
||||||
|
} },
|
||||||
|
.source_header = try allocator.dupe(u8, "SDL_gpu.h"),
|
||||||
|
.is_primary_header_decl = true,
|
||||||
|
});
|
||||||
|
const func_params = try allocator.alloc(patterns.ParamDecl, 3);
|
||||||
|
func_params[0] = .{ .name = try allocator.dupe(u8, "render_pass"), .type_name = try allocator.dupe(u8, "SDL_GPURenderPass *") };
|
||||||
|
func_params[1] = .{ .name = try allocator.dupe(u8, "binding"), .type_name = try allocator.dupe(u8, "const SDL_GPUBufferBinding *") };
|
||||||
|
func_params[2] = .{ .name = try allocator.dupe(u8, "index_element_size"), .type_name = try allocator.dupe(u8, "SDL_GPUIndexElementSize") };
|
||||||
|
try decls.append(allocator, .{
|
||||||
|
.decl = .{ .function_decl = .{
|
||||||
|
.name = try allocator.dupe(u8, "SDL_BindGPUIndexBuffer"),
|
||||||
|
.return_type = try allocator.dupe(u8, "void"),
|
||||||
|
.params = func_params,
|
||||||
|
.doc_comment = null,
|
||||||
|
} },
|
||||||
|
.source_header = try allocator.dupe(u8, "SDL_gpu.h"),
|
||||||
|
.is_primary_header_decl = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const output = try CodeGen.generate(allocator, .{
|
||||||
|
.decls = decls.items,
|
||||||
|
.module_name = "gpu",
|
||||||
|
.c_import_path = "../c.zig",
|
||||||
|
});
|
||||||
|
defer allocator.free(output);
|
||||||
|
|
||||||
|
try std.testing.expect(std.mem.indexOf(u8, output, "@intFromEnum(index_element_size)") != null);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -85,10 +85,21 @@ pub fn main() !void {
|
||||||
|
|
||||||
// Parse declarations
|
// Parse declarations
|
||||||
var scanner = patterns.Scanner.init(allocator, source);
|
var scanner = patterns.Scanner.init(allocator, source);
|
||||||
|
defer scanner.deinit();
|
||||||
const decls = try scanner.scan();
|
const decls = try scanner.scan();
|
||||||
defer freeDecls(allocator, decls);
|
defer freeDecls(allocator, decls);
|
||||||
|
const skipped = try scanner.takeSkipped();
|
||||||
|
defer {
|
||||||
|
for (skipped) |item| {
|
||||||
|
item.deinit(allocator);
|
||||||
|
}
|
||||||
|
allocator.free(skipped);
|
||||||
|
}
|
||||||
|
|
||||||
std.debug.print("Found {d} declarations\n", .{decls.len});
|
std.debug.print("Found {d} declarations\n", .{decls.len});
|
||||||
|
if (skipped.len > 0) {
|
||||||
|
std.debug.print("Skipped {d} unsupported declarations\n", .{skipped.len});
|
||||||
|
}
|
||||||
|
|
||||||
// Count each type
|
// Count each type
|
||||||
var opaque_count: usize = 0;
|
var opaque_count: usize = 0;
|
||||||
|
|
@ -281,6 +292,7 @@ pub fn main() !void {
|
||||||
.decls = all_resolved_decls.items,
|
.decls = all_resolved_decls.items,
|
||||||
.module_name = module_name,
|
.module_name = module_name,
|
||||||
.c_import_path = c_import_path,
|
.c_import_path = c_import_path,
|
||||||
|
.skipped = skipped,
|
||||||
});
|
});
|
||||||
defer allocator.free(output);
|
defer allocator.free(output);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -202,11 +202,22 @@ pub const ParamDecl = struct {
|
||||||
type_name: []const u8, // SDL_GPUShaderFormat
|
type_name: []const u8, // SDL_GPUShaderFormat
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const SkippedDecl = struct {
|
||||||
|
name: []const u8,
|
||||||
|
reason: []const u8,
|
||||||
|
|
||||||
|
pub fn deinit(self: SkippedDecl, allocator: Allocator) void {
|
||||||
|
allocator.free(self.name);
|
||||||
|
allocator.free(self.reason);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
pub const Scanner = struct {
|
pub const Scanner = struct {
|
||||||
source: []const u8,
|
source: []const u8,
|
||||||
pos: usize,
|
pos: usize,
|
||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
pending_doc_comment: ?[]const u8,
|
pending_doc_comment: ?[]const u8,
|
||||||
|
skipped: std.ArrayList(SkippedDecl),
|
||||||
|
|
||||||
pub fn init(allocator: Allocator, source: []const u8) Scanner {
|
pub fn init(allocator: Allocator, source: []const u8) Scanner {
|
||||||
return .{
|
return .{
|
||||||
|
|
@ -214,9 +225,17 @@ pub const Scanner = struct {
|
||||||
.pos = 0,
|
.pos = 0,
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.pending_doc_comment = null,
|
.pending_doc_comment = null,
|
||||||
|
.skipped = .empty,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Scanner) void {
|
||||||
|
for (self.skipped.items) |item| {
|
||||||
|
item.deinit(self.allocator);
|
||||||
|
}
|
||||||
|
self.skipped.deinit(self.allocator);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn scan(self: *Scanner) ![]Declaration {
|
pub fn scan(self: *Scanner) ![]Declaration {
|
||||||
var decls = try std.ArrayList(Declaration).initCapacity(self.allocator, 100);
|
var decls = try std.ArrayList(Declaration).initCapacity(self.allocator, 100);
|
||||||
|
|
||||||
|
|
@ -226,6 +245,10 @@ pub const Scanner = struct {
|
||||||
self.pending_doc_comment = comment;
|
self.pending_doc_comment = comment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (try self.scanUnsupportedMacro()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Try each pattern - order matters!
|
// Try each pattern - order matters!
|
||||||
// Try opaque first (typedef struct SDL_X SDL_X;)
|
// Try opaque first (typedef struct SDL_X SDL_X;)
|
||||||
if (try self.scanOpaque()) |opaque_decl| {
|
if (try self.scanOpaque()) |opaque_decl| {
|
||||||
|
|
@ -260,6 +283,50 @@ pub const Scanner = struct {
|
||||||
return try decls.toOwnedSlice(self.allocator);
|
return try decls.toOwnedSlice(self.allocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn takeSkipped(self: *Scanner) ![]SkippedDecl {
|
||||||
|
const items = try self.skipped.toOwnedSlice(self.allocator);
|
||||||
|
self.skipped = .empty;
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn recordSkippedDecl(self: *Scanner, name: []const u8, reason: []const u8) !void {
|
||||||
|
try self.skipped.append(self.allocator, .{
|
||||||
|
.name = try self.allocator.dupe(u8, name),
|
||||||
|
.reason = try self.allocator.dupe(u8, reason),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scanUnsupportedMacro(self: *Scanner) !bool {
|
||||||
|
if (!std.mem.startsWith(u8, self.source[self.pos..], "#define ")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const start = self.pos;
|
||||||
|
const line = try self.readLine();
|
||||||
|
defer self.allocator.free(line);
|
||||||
|
|
||||||
|
var parts = std.mem.tokenizeScalar(u8, line, ' ');
|
||||||
|
const define_name = parts.next() orelse {
|
||||||
|
self.pos = start;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (std.mem.indexOfScalar(u8, define_name, '(') == null) {
|
||||||
|
self.pos = start;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const macro_name = define_name[0 .. std.mem.indexOfScalar(u8, define_name, '(').?];
|
||||||
|
try self.recordSkippedDecl(macro_name, "function-like macro is not emitted");
|
||||||
|
|
||||||
|
if (self.pending_doc_comment) |comment| {
|
||||||
|
self.allocator.free(comment);
|
||||||
|
self.pending_doc_comment = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Pattern: typedef struct SDL_Foo SDL_Foo;
|
// Pattern: typedef struct SDL_Foo SDL_Foo;
|
||||||
fn scanOpaque(self: *Scanner) !?OpaqueType {
|
fn scanOpaque(self: *Scanner) !?OpaqueType {
|
||||||
const start = self.pos;
|
const start = self.pos;
|
||||||
|
|
@ -1692,6 +1759,15 @@ pub const Scanner = struct {
|
||||||
for (vararg_macros) |macro| {
|
for (vararg_macros) |macro| {
|
||||||
if (std.mem.indexOf(u8, text, macro)) |pos| {
|
if (std.mem.indexOf(u8, text, macro)) |pos| {
|
||||||
_ = pos;
|
_ = pos;
|
||||||
|
if (std.mem.indexOf(u8, text, "SDLCALL ")) |sdlcall_pos| {
|
||||||
|
const after_sdlcall = text[sdlcall_pos + 8 ..];
|
||||||
|
if (std.mem.indexOfScalar(u8, after_sdlcall, '(')) |paren_pos| {
|
||||||
|
const func_name = std.mem.trim(u8, after_sdlcall[0..paren_pos], " \t\n*");
|
||||||
|
if (func_name.len > 0) {
|
||||||
|
try self.recordSkippedDecl(func_name, "varargs function is not representable in Zig");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return null; // we dont attempt to generate vararg functions.
|
return null; // we dont attempt to generate vararg functions.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
279
src/types.zig
279
src/types.zig
|
|
@ -1,11 +1,8 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const io = @import("io");
|
|
||||||
|
|
||||||
var fmtBuffer: [256]u8 = undefined;
|
|
||||||
/// Convert C type to Zig type
|
/// Convert C type to Zig type
|
||||||
/// Simple table-based conversion for SDL3 types
|
/// Simple table-based conversion for SDL3 types
|
||||||
pub fn convertType(c_type: []const u8, allocator: Allocator) ![]const u8 {
|
pub fn convertType(c_type: []const u8, allocator: Allocator) Allocator.Error![]const u8 {
|
||||||
const trimmed = std.mem.trim(u8, c_type, " \t");
|
const trimmed = std.mem.trim(u8, c_type, " \t");
|
||||||
|
|
||||||
// Handle opaque struct pointers: "struct X *" -> "*anyopaque"
|
// Handle opaque struct pointers: "struct X *" -> "*anyopaque"
|
||||||
|
|
@ -13,17 +10,17 @@ pub fn convertType(c_type: []const u8, allocator: Allocator) ![]const u8 {
|
||||||
return try allocator.dupe(u8, "*anyopaque");
|
return try allocator.dupe(u8, "*anyopaque");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle function pointers: For now, just return as placeholder until we implement full conversion
|
// Handle function pointers used in callbacks and interface structs.
|
||||||
if (std.mem.indexOf(u8, trimmed, "(SDLCALL *") != null or std.mem.indexOf(u8, trimmed, "(*") != null) {
|
if (std.mem.indexOf(u8, trimmed, "(SDLCALL *") != null or std.mem.indexOf(u8, trimmed, "(*") != null) {
|
||||||
// TODO: Implement full function pointer conversion
|
return try convertFunctionPointerType(trimmed, allocator);
|
||||||
// For now, return a placeholder type
|
|
||||||
return try std.fmt.allocPrint(allocator, "?*const anyopaque", .{});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle array types: "Uint8[2]" -> "[2]u8"
|
// Handle array types: "Uint8[2]" -> "[2]u8"
|
||||||
if (std.mem.indexOf(u8, trimmed, "[")) |bracket_pos| {
|
if (std.mem.indexOf(u8, trimmed, "[")) |bracket_pos| {
|
||||||
const base_type = std.mem.trim(u8, trimmed[0..bracket_pos], " \t");
|
const base_type = std.mem.trim(u8, trimmed[0..bracket_pos], " \t");
|
||||||
var array_part = trimmed[bracket_pos..]; // "[2]"
|
var array_part = trimmed[bracket_pos..]; // "[2]"
|
||||||
|
var owned_array_part: ?[]const u8 = null;
|
||||||
|
defer if (owned_array_part) |slice| allocator.free(slice);
|
||||||
|
|
||||||
// Recursively convert the base type
|
// Recursively convert the base type
|
||||||
const zig_base = try convertType(base_type, allocator);
|
const zig_base = try convertType(base_type, allocator);
|
||||||
|
|
@ -39,13 +36,8 @@ pub fn convertType(c_type: []const u8, allocator: Allocator) ![]const u8 {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (alias_to_c) {
|
if (alias_to_c) {
|
||||||
var b = std.io.fixedBufferStream(&fmtBuffer);
|
owned_array_part = try std.fmt.allocPrint(allocator, "[c.{s}]", .{inner});
|
||||||
_ = try b.write("[");
|
array_part = owned_array_part.?;
|
||||||
_ = try b.write("c.");
|
|
||||||
_ = try b.write(inner);
|
|
||||||
_ = try b.write("]");
|
|
||||||
array_part = b.getWritten();
|
|
||||||
std.debug.print("arrya_part = {s}\n", .{array_part});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -97,12 +89,12 @@ pub fn convertType(c_type: []const u8, allocator: Allocator) ![]const u8 {
|
||||||
// Match "SDL_Type *const *" (no space before const)
|
// Match "SDL_Type *const *" (no space before const)
|
||||||
if (std.mem.indexOf(u8, trimmed, " *const *")) |pos| {
|
if (std.mem.indexOf(u8, trimmed, " *const *")) |pos| {
|
||||||
const base_type = trimmed[4..pos]; // Remove SDL_ prefix and get type
|
const base_type = trimmed[4..pos]; // Remove SDL_ prefix and get type
|
||||||
return std.fmt.allocPrint(allocator, "[*c]?*const {s}", .{base_type});
|
return std.fmt.allocPrint(allocator, "[*c]const ?*{s}", .{base_type});
|
||||||
}
|
}
|
||||||
// Match "SDL_Type * const *" (space before const)
|
// Match "SDL_Type * const *" (space before const)
|
||||||
if (std.mem.indexOf(u8, trimmed, " * const *")) |pos| {
|
if (std.mem.indexOf(u8, trimmed, " * const *")) |pos| {
|
||||||
const base_type = trimmed[4..pos]; // Remove SDL_ prefix and get type
|
const base_type = trimmed[4..pos]; // Remove SDL_ prefix and get type
|
||||||
return std.fmt.allocPrint(allocator, "[*c]?*const {s}", .{base_type});
|
return std.fmt.allocPrint(allocator, "[*c]const ?*{s}", .{base_type});
|
||||||
}
|
}
|
||||||
if (std.mem.indexOf(u8, trimmed, " **")) |pos| {
|
if (std.mem.indexOf(u8, trimmed, " **")) |pos| {
|
||||||
const base_type = trimmed[4..pos]; // Remove SDL_ prefix and get type
|
const base_type = trimmed[4..pos]; // Remove SDL_ prefix and get type
|
||||||
|
|
@ -127,11 +119,23 @@ pub fn convertType(c_type: []const u8, allocator: Allocator) ![]const u8 {
|
||||||
if (std.mem.eql(u8, trimmed, "Sint8 *")) return try allocator.dupe(u8, "*i8");
|
if (std.mem.eql(u8, trimmed, "Sint8 *")) return try allocator.dupe(u8, "*i8");
|
||||||
if (std.mem.eql(u8, trimmed, "Sint16 *")) return try allocator.dupe(u8, "*i16");
|
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, "Sint32 *")) return try allocator.dupe(u8, "*i32");
|
||||||
|
if (std.mem.eql(u8, trimmed, "Sint64 *")) return try allocator.dupe(u8, "*i64");
|
||||||
|
if (std.mem.eql(u8, trimmed, "const Sint64 *")) return try allocator.dupe(u8, "*const i64");
|
||||||
if (std.mem.eql(u8, trimmed, "const bool *")) return try allocator.dupe(u8, "*const bool");
|
if (std.mem.eql(u8, trimmed, "const bool *")) return try allocator.dupe(u8, "*const bool");
|
||||||
|
|
||||||
if (std.mem.startsWith(u8, trimmed, "const ")) {
|
if (std.mem.startsWith(u8, trimmed, "const ")) {
|
||||||
const rest = trimmed[6..];
|
const rest = trimmed[6..];
|
||||||
if (std.mem.startsWith(u8, rest, "SDL_")) {
|
if (std.mem.startsWith(u8, rest, "SDL_")) {
|
||||||
|
if (std.mem.indexOf(u8, rest, " *const *")) |pos| {
|
||||||
|
const base_type = rest[0..pos];
|
||||||
|
const zig_type = base_type[4..];
|
||||||
|
return std.fmt.allocPrint(allocator, "[*c]const ?*const {s}", .{zig_type});
|
||||||
|
}
|
||||||
|
if (std.mem.indexOf(u8, rest, " * const *")) |pos| {
|
||||||
|
const base_type = rest[0..pos];
|
||||||
|
const zig_type = base_type[4..];
|
||||||
|
return std.fmt.allocPrint(allocator, "[*c]const ?*const {s}", .{zig_type});
|
||||||
|
}
|
||||||
if (std.mem.indexOfScalar(u8, rest, '*')) |star_pos| {
|
if (std.mem.indexOfScalar(u8, rest, '*')) |star_pos| {
|
||||||
const base_type = std.mem.trim(u8, rest[0..star_pos], " \t");
|
const base_type = std.mem.trim(u8, rest[0..star_pos], " \t");
|
||||||
const star_count = std.mem.count(u8, rest[star_pos..], "*");
|
const star_count = std.mem.count(u8, rest[star_pos..], "*");
|
||||||
|
|
@ -152,20 +156,6 @@ pub fn convertType(c_type: []const u8, allocator: Allocator) ![]const u8 {
|
||||||
return std.fmt.allocPrint(allocator, "[*c]?*const {s}", .{zig_type});
|
return std.fmt.allocPrint(allocator, "[*c]?*const {s}", .{zig_type});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (std.mem.indexOf(u8, rest, " *const *")) |pos| {
|
|
||||||
const base_type = rest[0..pos];
|
|
||||||
if (std.mem.startsWith(u8, base_type, "SDL_")) {
|
|
||||||
const zig_type = base_type[4..];
|
|
||||||
return std.fmt.allocPrint(allocator, "[*c]?*const {s}", .{zig_type});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (std.mem.indexOf(u8, rest, " * const *")) |pos| {
|
|
||||||
const base_type = rest[0..pos];
|
|
||||||
if (std.mem.startsWith(u8, base_type, "SDL_")) {
|
|
||||||
const zig_type = base_type[4..];
|
|
||||||
return std.fmt.allocPrint(allocator, "[*c]?*const {s}", .{zig_type});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (std.mem.endsWith(u8, rest, " *") or std.mem.endsWith(u8, rest, "*")) {
|
if (std.mem.endsWith(u8, rest, " *") or std.mem.endsWith(u8, rest, "*")) {
|
||||||
const base_type = if (std.mem.endsWith(u8, rest, " *"))
|
const base_type = if (std.mem.endsWith(u8, rest, " *"))
|
||||||
rest[0 .. rest.len - 2]
|
rest[0 .. rest.len - 2]
|
||||||
|
|
@ -220,39 +210,121 @@ pub fn convertType(c_type: []const u8, allocator: Allocator) ![]const u8 {
|
||||||
return try allocator.dupe(u8, trimmed);
|
return try allocator.dupe(u8, trimmed);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determine the appropriate cast for a given type when calling C functions
|
pub fn convertStructFieldType(c_type: []const u8, allocator: Allocator) Allocator.Error![]const u8 {
|
||||||
pub fn getCastType(zig_type: []const u8) CastType {
|
const trimmed = std.mem.trim(u8, c_type, " \t");
|
||||||
|
|
||||||
|
if (std.mem.indexOf(u8, trimmed, "(SDLCALL *") != null or std.mem.indexOf(u8, trimmed, "(*") != null) {
|
||||||
|
return try convertType(trimmed, allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std.mem.indexOfScalar(u8, trimmed, '[') != null) {
|
||||||
|
return try convertType(trimmed, allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std.mem.indexOfScalar(u8, trimmed, '*') == null) {
|
||||||
|
return try convertType(trimmed, allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
var rest = trimmed;
|
||||||
|
var pointee_const = false;
|
||||||
|
if (std.mem.startsWith(u8, rest, "const ")) {
|
||||||
|
pointee_const = true;
|
||||||
|
rest = std.mem.trim(u8, rest[6..], " \t");
|
||||||
|
}
|
||||||
|
|
||||||
|
const first_star = std.mem.indexOfScalar(u8, rest, '*') orelse return try convertType(trimmed, allocator);
|
||||||
|
const base_type = std.mem.trim(u8, rest[0..first_star], " \t");
|
||||||
|
const star_count = std.mem.count(u8, rest[first_star..], "*");
|
||||||
|
|
||||||
|
const zig_base = if (std.mem.eql(u8, base_type, "void"))
|
||||||
|
try allocator.dupe(u8, "anyopaque")
|
||||||
|
else
|
||||||
|
try convertType(base_type, allocator);
|
||||||
|
defer allocator.free(zig_base);
|
||||||
|
|
||||||
|
var result: std.ArrayList(u8) = .empty;
|
||||||
|
defer result.deinit(allocator);
|
||||||
|
|
||||||
|
for (0..star_count) |_| {
|
||||||
|
try result.appendSlice(allocator, "[*c]");
|
||||||
|
}
|
||||||
|
if (pointee_const) {
|
||||||
|
try result.appendSlice(allocator, "const ");
|
||||||
|
}
|
||||||
|
try result.appendSlice(allocator, zig_base);
|
||||||
|
|
||||||
|
return try result.toOwnedSlice(allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn isOpaquePointerLike(zig_type: []const u8) bool {
|
||||||
|
if (!(std.mem.startsWith(u8, zig_type, "?*") or std.mem.startsWith(u8, zig_type, "*"))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std.mem.indexOf(u8, zig_type, "anyopaque") != null or
|
||||||
|
std.mem.startsWith(u8, zig_type, "[*c]") or
|
||||||
|
std.mem.startsWith(u8, zig_type, "?[*c]"))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn isEnumLikeType(zig_type: []const u8) bool {
|
||||||
|
if (isPackedFlagsLikeType(zig_type)) return false;
|
||||||
|
|
||||||
|
return std.mem.indexOf(u8, zig_type, "Type") != null or
|
||||||
|
std.mem.indexOf(u8, zig_type, "Size") != null or
|
||||||
|
std.mem.indexOf(u8, zig_type, "Mode") != null or
|
||||||
|
std.mem.indexOf(u8, zig_type, "Op") != null or
|
||||||
|
std.mem.endsWith(u8, zig_type, "Format");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn isPackedFlagsLikeType(zig_type: []const u8) bool {
|
||||||
|
return std.mem.endsWith(u8, zig_type, "Flags") or
|
||||||
|
std.mem.eql(u8, zig_type, "GPUShaderFormat") or
|
||||||
|
std.mem.eql(u8, zig_type, "FlipMode");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Determine the appropriate cast for a Zig parameter passed into a C function.
|
||||||
|
pub fn getParamCastType(zig_type: []const u8) CastType {
|
||||||
// Bool needs @bitCast
|
// Bool needs @bitCast
|
||||||
if (std.mem.eql(u8, zig_type, "bool")) {
|
if (std.mem.eql(u8, zig_type, "bool")) {
|
||||||
return .bit_cast;
|
return .bit_cast;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Opaque pointers need @ptrCast (both ?*Type and *Type)
|
// Opaque pointers need @ptrCast (both ?*Type and *Type)
|
||||||
// Skip [*c] (C pointers) and *anyopaque
|
if (isOpaquePointerLike(zig_type)) {
|
||||||
if (std.mem.startsWith(u8, zig_type, "?*") or std.mem.startsWith(u8, zig_type, "*")) {
|
|
||||||
// Exclude anyopaque and C pointers
|
|
||||||
if (std.mem.indexOf(u8, zig_type, "anyopaque") != null or
|
|
||||||
std.mem.startsWith(u8, zig_type, "[*c]") or
|
|
||||||
std.mem.startsWith(u8, zig_type, "?[*c]")) {
|
|
||||||
return .none;
|
|
||||||
}
|
|
||||||
return .ptr_cast;
|
return .ptr_cast;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enums need @intFromEnum
|
if (isEnumLikeType(zig_type)) {
|
||||||
// We'll detect these by naming convention or explicit marking
|
|
||||||
// For now, assume types ending in certain patterns are enums
|
|
||||||
if (std.mem.indexOf(u8, zig_type, "Type") != null or
|
|
||||||
std.mem.indexOf(u8, zig_type, "Mode") != null or
|
|
||||||
std.mem.indexOf(u8, zig_type, "Op") != null)
|
|
||||||
{
|
|
||||||
return .int_from_enum;
|
return .int_from_enum;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flags (packed structs) need @bitCast
|
if (isPackedFlagsLikeType(zig_type)) {
|
||||||
if (std.mem.endsWith(u8, zig_type, "Flags") or
|
return .bit_cast;
|
||||||
std.mem.endsWith(u8, zig_type, "Format"))
|
}
|
||||||
{
|
|
||||||
|
return .none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Determine the appropriate cast for a C return value exposed as a Zig type.
|
||||||
|
pub fn getReturnCastType(zig_type: []const u8) CastType {
|
||||||
|
if (std.mem.eql(u8, zig_type, "bool")) {
|
||||||
|
return .bit_cast;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isOpaquePointerLike(zig_type)) {
|
||||||
|
return .ptr_cast;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isEnumLikeType(zig_type)) {
|
||||||
|
return .enum_from_int;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPackedFlagsLikeType(zig_type)) {
|
||||||
return .bit_cast;
|
return .bit_cast;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -261,7 +333,7 @@ pub fn getCastType(zig_type: []const u8) CastType {
|
||||||
|
|
||||||
/// Convert C function pointer type to Zig function pointer syntax
|
/// Convert C function pointer type to Zig function pointer syntax
|
||||||
/// Example: Sint64 (SDLCALL *size)(void *userdata) -> *const fn (?*anyopaque) callconv(.C) i64
|
/// Example: Sint64 (SDLCALL *size)(void *userdata) -> *const fn (?*anyopaque) callconv(.C) i64
|
||||||
fn convertFunctionPointerType(c_type: []const u8, allocator: Allocator) ![]const u8 {
|
fn convertFunctionPointerType(c_type: []const u8, allocator: Allocator) Allocator.Error![]const u8 {
|
||||||
// Pattern: ReturnType (SDLCALL *name)(params) or ReturnType (*name)(params)
|
// Pattern: ReturnType (SDLCALL *name)(params) or ReturnType (*name)(params)
|
||||||
|
|
||||||
// Find the return type (everything before the opening paren)
|
// Find the return type (everything before the opening paren)
|
||||||
|
|
@ -284,12 +356,12 @@ fn convertFunctionPointerType(c_type: []const u8, allocator: Allocator) ![]const
|
||||||
defer allocator.free(zig_return);
|
defer allocator.free(zig_return);
|
||||||
|
|
||||||
// Convert parameters
|
// Convert parameters
|
||||||
var params_list = std.ArrayList([]const u8).init(allocator);
|
var params_list: std.ArrayList([]const u8) = .empty;
|
||||||
defer {
|
defer {
|
||||||
for (params_list.items) |param| {
|
for (params_list.items) |param| {
|
||||||
allocator.free(param);
|
allocator.free(param);
|
||||||
}
|
}
|
||||||
params_list.deinit();
|
params_list.deinit(allocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse comma-separated parameters
|
// Parse comma-separated parameters
|
||||||
|
|
@ -298,43 +370,53 @@ fn convertFunctionPointerType(c_type: []const u8, allocator: Allocator) ![]const
|
||||||
const trimmed_param = std.mem.trim(u8, param, " \t");
|
const trimmed_param = std.mem.trim(u8, param, " \t");
|
||||||
if (trimmed_param.len == 0) continue;
|
if (trimmed_param.len == 0) continue;
|
||||||
|
|
||||||
// Extract just the type (remove parameter name if present)
|
const param_type = stripParamName(trimmed_param);
|
||||||
// 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 *)
|
|
||||||
const after_space = trimmed_param[space_pos + 1 ..];
|
|
||||||
if (after_space.len > 0 and after_space[0] != '*') {
|
|
||||||
param_type = std.mem.trimRight(u8, trimmed_param[0..space_pos], " \t");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't recursively convert function pointers in params
|
// Don't recursively convert function pointers in params
|
||||||
const zig_param = if (std.mem.indexOf(u8, param_type, "(") != null)
|
const zig_param = if (std.mem.indexOf(u8, param_type, "(") != null)
|
||||||
try allocator.dupe(u8, param_type)
|
try allocator.dupe(u8, param_type)
|
||||||
else
|
else
|
||||||
try convertType(param_type, allocator);
|
try convertType(param_type, allocator);
|
||||||
try params_list.append(zig_param);
|
try params_list.append(allocator, zig_param);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build Zig function pointer type
|
// Build Zig function pointer type
|
||||||
var result = std.ArrayList(u8).init(allocator);
|
var result: std.ArrayList(u8) = .empty;
|
||||||
defer result.deinit();
|
defer result.deinit(allocator);
|
||||||
|
|
||||||
try result.appendSlice("?*const fn (");
|
try result.appendSlice(allocator, "?*const fn(");
|
||||||
|
|
||||||
for (params_list.items, 0..) |param, i| {
|
for (params_list.items, 0..) |param, i| {
|
||||||
if (i > 0) try result.appendSlice(", ");
|
if (i > 0) try result.appendSlice(allocator, ", ");
|
||||||
try result.appendSlice(param);
|
try result.appendSlice(allocator, param);
|
||||||
}
|
}
|
||||||
|
|
||||||
try result.appendSlice(") callconv(.C) ");
|
try result.appendSlice(allocator, ") callconv(.C) ");
|
||||||
try result.appendSlice(zig_return);
|
try result.appendSlice(allocator, zig_return);
|
||||||
|
|
||||||
return try result.toOwnedSlice();
|
return try result.toOwnedSlice(allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stripParamName(param: []const u8) []const u8 {
|
||||||
|
if (param.len == 0) return param;
|
||||||
|
if (std.mem.eql(u8, param, "...")) return param;
|
||||||
|
|
||||||
|
var ident_start = param.len;
|
||||||
|
while (ident_start > 0) {
|
||||||
|
const c = param[ident_start - 1];
|
||||||
|
if (std.ascii.isAlphanumeric(c) or c == '_') {
|
||||||
|
ident_start -= 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ident_start == param.len or ident_start == 0) return param;
|
||||||
|
|
||||||
|
const before_name = std.mem.trimRight(u8, param[0..ident_start], " \t");
|
||||||
|
if (before_name.len == 0) return param;
|
||||||
|
|
||||||
|
return before_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const CastType = enum {
|
pub const CastType = enum {
|
||||||
|
|
@ -352,3 +434,46 @@ test "convert const SDL double pointer" {
|
||||||
|
|
||||||
try std.testing.expectEqualStrings("[*c]?*const TrayEntry", zig_type);
|
try std.testing.expectEqualStrings("[*c]?*const TrayEntry", zig_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "convert function pointer callback type" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
const zig_type = try convertType("Sint64 (SDLCALL *size)(void *userdata)", allocator);
|
||||||
|
defer allocator.free(zig_type);
|
||||||
|
|
||||||
|
try std.testing.expectEqualStrings("?*const fn(?*anyopaque) callconv(.C) i64", zig_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "convert SDL pointer arrays preserves const on outer sequence" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
|
||||||
|
const zig_type = try convertType("SDL_GPUBuffer *const *", allocator);
|
||||||
|
defer allocator.free(zig_type);
|
||||||
|
try std.testing.expectEqualStrings("[*c]const ?*GPUBuffer", zig_type);
|
||||||
|
|
||||||
|
const const_zig_type = try convertType("const SDL_GPUBuffer *const *", allocator);
|
||||||
|
defer allocator.free(const_zig_type);
|
||||||
|
try std.testing.expectEqualStrings("[*c]const ?*const GPUBuffer", const_zig_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "enum cast direction differs for params and returns" {
|
||||||
|
try std.testing.expectEqual(.int_from_enum, getParamCastType("GPUTextureFormat"));
|
||||||
|
try std.testing.expectEqual(.enum_from_int, getReturnCastType("GPUTextureFormat"));
|
||||||
|
try std.testing.expectEqual(.int_from_enum, getParamCastType("GPUIndexElementSize"));
|
||||||
|
try std.testing.expectEqual(.enum_from_int, getReturnCastType("GPUIndexElementSize"));
|
||||||
|
try std.testing.expectEqual(.bit_cast, getParamCastType("GPUShaderFormat"));
|
||||||
|
try std.testing.expectEqual(.bit_cast, getReturnCastType("GPUShaderFormat"));
|
||||||
|
try std.testing.expectEqual(.bit_cast, getParamCastType("FlipMode"));
|
||||||
|
try std.testing.expectEqual(.bit_cast, getReturnCastType("FlipMode"));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "struct fields use C pointers" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
|
||||||
|
const single = try convertStructFieldType("const SDL_GPUColorTargetDescription *", allocator);
|
||||||
|
defer allocator.free(single);
|
||||||
|
try std.testing.expectEqualStrings("[*c]const GPUColorTargetDescription", single);
|
||||||
|
|
||||||
|
const generic = try convertStructFieldType("const void *", allocator);
|
||||||
|
defer allocator.free(generic);
|
||||||
|
try std.testing.expectEqualStrings("[*c]const anyopaque", generic);
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue