sdlparser-scrap/src/codegen.zig

991 lines
42 KiB
Zig

const std = @import("std");
const Allocator = std.mem.Allocator;
const patterns = @import("patterns.zig");
const naming = @import("naming.zig");
const resolved_decl = @import("resolved_decl.zig");
const types = @import("types.zig");
const EnumDecl = patterns.EnumDecl;
const FlagDecl = patterns.FlagDecl;
const OpaqueType = patterns.OpaqueType;
const ResolvedDecl = resolved_decl.ResolvedDecl;
const StructDecl = patterns.StructDecl;
const ImportedModule = struct {
module_name: []const u8,
import_name: []const u8,
};
const TypeAlias = struct {
name: []const u8,
import_name: []const u8,
target_name: []const u8,
};
pub const CodeGenOptions = struct {
decls: []const ResolvedDecl,
module_name: []const u8,
c_import_path: []const u8 = "c.zig",
skipped: []const patterns.SkippedDecl = &.{},
};
pub const CodeGen = struct {
decls: []const ResolvedDecl,
module_name: []const u8,
c_import_path: []const u8,
skipped: []const patterns.SkippedDecl,
allocator: Allocator,
output: std.ArrayList(u8),
opaque_methods: std.StringHashMap(std.ArrayList(patterns.FunctionDecl)),
import_modules: std.ArrayList(ImportedModule),
aliases: std.ArrayList(TypeAlias),
pub fn generate(allocator: Allocator, options: CodeGenOptions) ![]const u8 {
var gen = CodeGen{
.decls = options.decls,
.module_name = options.module_name,
.c_import_path = options.c_import_path,
.skipped = options.skipped,
.allocator = allocator,
.output = try std.ArrayList(u8).initCapacity(allocator, 4096),
.opaque_methods = std.StringHashMap(std.ArrayList(patterns.FunctionDecl)).init(allocator),
.import_modules = .empty,
.aliases = .empty,
};
defer gen.deinit();
try gen.planDependencies();
try gen.categorizeDeclarations();
try gen.writeHeader();
try gen.writeDeclarations();
return try gen.output.toOwnedSlice(allocator);
}
fn deinit(self: *CodeGen) void {
var method_it = self.opaque_methods.valueIterator();
while (method_it.next()) |methods| {
methods.deinit(self.allocator);
}
self.opaque_methods.deinit();
for (self.import_modules.items) |module| {
self.allocator.free(module.module_name);
self.allocator.free(module.import_name);
}
self.import_modules.deinit(self.allocator);
for (self.aliases.items) |alias_decl| {
self.allocator.free(alias_decl.import_name);
}
self.aliases.deinit(self.allocator);
self.output.deinit(self.allocator);
}
fn planDependencies(self: *CodeGen) !void {
var seen_modules = std.StringHashMap(void).init(self.allocator);
defer {
var module_it = seen_modules.keyIterator();
while (module_it.next()) |key| {
self.allocator.free(key.*);
}
seen_modules.deinit();
}
var seen_aliases = std.StringHashMap(void).init(self.allocator);
defer {
var alias_it = seen_aliases.keyIterator();
while (alias_it.next()) |key| {
self.allocator.free(key.*);
}
seen_aliases.deinit();
}
for (self.decls) |resolved| {
const type_name = resolved.typeName() orelse continue;
const owner_module = try resolved_decl.headerToModuleNameAlloc(self.allocator, resolved.source_header);
defer self.allocator.free(owner_module);
if (std.mem.eql(u8, owner_module, self.module_name)) continue;
if (!seen_modules.contains(owner_module)) {
const module_name = try self.allocator.dupe(u8, owner_module);
const import_name = try std.fmt.allocPrint(self.allocator, "{s}_api", .{owner_module});
try seen_modules.put(try self.allocator.dupe(u8, owner_module), {});
try self.import_modules.append(self.allocator, .{
.module_name = module_name,
.import_name = import_name,
});
}
const alias_name = naming.typeNameToZig(type_name);
if (!seen_aliases.contains(alias_name)) {
try seen_aliases.put(try self.allocator.dupe(u8, alias_name), {});
const import_name = try std.fmt.allocPrint(self.allocator, "{s}_api", .{owner_module});
try self.aliases.append(self.allocator, .{
.name = alias_name,
.import_name = import_name,
.target_name = alias_name,
});
}
}
}
fn categorizeDeclarations(self: *CodeGen) !void {
var opaque_names: std.ArrayList([]const u8) = .empty;
defer opaque_names.deinit(self.allocator);
for (self.decls) |resolved| {
if (!try self.isOwnedDecl(resolved)) continue;
if (resolved.decl == .opaque_type) {
const zig_name = naming.typeNameToZig(resolved.decl.opaque_type.name);
try opaque_names.append(self.allocator, zig_name);
try self.opaque_methods.put(zig_name, .empty);
}
}
for (self.decls) |resolved| {
if (!try self.isOwnedDecl(resolved)) continue;
if (resolved.decl != .function_decl) continue;
const func = resolved.decl.function_decl;
if (func.params.len == 0) continue;
const first_param_type = try types.convertType(func.params[0].type_name, self.allocator);
defer self.allocator.free(first_param_type);
for (opaque_names.items) |opaque_name| {
const opt_ptr = try std.fmt.allocPrint(self.allocator, "?*{s}", .{opaque_name});
defer self.allocator.free(opt_ptr);
const ptr = try std.fmt.allocPrint(self.allocator, "*{s}", .{opaque_name});
defer self.allocator.free(ptr);
if (std.mem.eql(u8, first_param_type, opt_ptr) or std.mem.eql(u8, first_param_type, ptr)) {
var methods = self.opaque_methods.getPtr(opaque_name).?;
try methods.append(self.allocator, func);
break;
}
}
}
}
fn isOwnedDecl(self: *CodeGen, resolved: ResolvedDecl) !bool {
if (!resolved.is_primary_header_decl and resolved.typeName() == null) return true;
const owner_module = try resolved_decl.headerToModuleNameAlloc(self.allocator, resolved.source_header);
defer self.allocator.free(owner_module);
return std.mem.eql(u8, owner_module, self.module_name);
}
fn writeHeader(self: *CodeGen) !void {
try self.output.writer(self.allocator).print(
\\const std = @import("std");
\\pub const c = @import("{s}").c;
\\
, .{self.c_import_path});
for (self.import_modules.items) |module| {
try self.output.writer(self.allocator).print("const {s} = @import(\"{s}.zig\");\n", .{
module.import_name,
module.module_name,
});
}
if (self.import_modules.items.len > 0) {
try self.output.appendSlice(self.allocator, "\n");
}
for (self.aliases.items) |alias_decl| {
try self.output.writer(self.allocator).print("pub const {s} = {s}.{s};\n", .{
alias_decl.name,
alias_decl.import_name,
alias_decl.target_name,
});
}
if (self.aliases.items.len > 0) {
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 {
for (self.decls) |resolved| {
if (!try self.isOwnedDecl(resolved)) continue;
switch (resolved.decl) {
.opaque_type => |opaque_decl| try self.writeOpaqueWithMethods(opaque_decl),
.typedef_decl => |typedef_decl| try self.writeTypedef(typedef_decl),
.function_pointer_decl => |func_ptr_decl| try self.writeFunctionPointer(func_ptr_decl),
.c_type_alias => |alias| try self.writeCTypeAlias(alias),
.enum_decl => |enum_decl| try self.writeEnum(enum_decl),
.struct_decl => |struct_decl| try self.writeStruct(struct_decl),
.union_decl => |union_decl| try self.writeUnion(union_decl),
.flag_decl => |flag_decl| try self.writeFlags(flag_decl),
.function_decl => |func| {
if (try self.isStandaloneFunction(func)) {
try self.writeFunction(func);
}
},
}
}
}
fn isStandaloneFunction(self: *CodeGen, func: patterns.FunctionDecl) !bool {
if (func.params.len == 0) return true;
const first_param_type = try types.convertType(func.params[0].type_name, self.allocator);
defer self.allocator.free(first_param_type);
var it = self.opaque_methods.keyIterator();
while (it.next()) |opaque_name| {
const opt_ptr = try std.fmt.allocPrint(self.allocator, "?*{s}", .{opaque_name.*});
defer self.allocator.free(opt_ptr);
const ptr = try std.fmt.allocPrint(self.allocator, "*{s}", .{opaque_name.*});
defer self.allocator.free(ptr);
if (std.mem.eql(u8, first_param_type, opt_ptr) or std.mem.eql(u8, first_param_type, ptr)) return false;
}
return true;
}
fn writeOpaqueWithMethods(self: *CodeGen, opaque_type: OpaqueType) !void {
const zig_name = naming.typeNameToZig(opaque_type.name);
if (opaque_type.doc_comment) |doc| try self.writeDocComment(doc);
if (self.opaque_methods.get(zig_name)) |method_list| {
if (method_list.items.len > 0) {
try self.output.writer(self.allocator).print("pub const {s} = opaque {{\n", .{zig_name});
for (method_list.items) |func| {
try self.writeFunctionAsMethod(func, zig_name);
}
try self.output.appendSlice(self.allocator, "};\n\n");
return;
}
}
try self.output.writer(self.allocator).print("pub const {s} = opaque {{}};\n\n", .{zig_name});
}
fn writeTypedef(self: *CodeGen, typedef_decl: patterns.TypedefDecl) !void {
if (typedef_decl.doc_comment) |doc| try self.writeDocComment(doc);
const zig_name = naming.typeNameToZig(typedef_decl.name);
const zig_type = try types.convertType(typedef_decl.underlying_type, self.allocator);
defer self.allocator.free(zig_type);
try self.output.appendSlice(self.allocator, "pub const ");
try self.output.appendSlice(self.allocator, zig_name);
try self.output.appendSlice(self.allocator, " = ");
try self.output.appendSlice(self.allocator, zig_type);
try self.output.appendSlice(self.allocator, ";\n\n");
}
fn writeFunctionPointer(self: *CodeGen, func_ptr_decl: patterns.FunctionPointerDecl) !void {
if (func_ptr_decl.doc_comment) |doc| try self.writeDocComment(doc);
const zig_name = naming.typeNameToZig(func_ptr_decl.name);
const return_type = try types.convertType(func_ptr_decl.return_type, self.allocator);
defer self.allocator.free(return_type);
try self.output.writer(self.allocator).print("pub const {s} = *const fn(", .{zig_name});
for (func_ptr_decl.params, 0..) |param, i| {
if (i > 0) try self.output.appendSlice(self.allocator, ", ");
const param_type = try types.convertType(param.type_name, self.allocator);
defer self.allocator.free(param_type);
try self.output.writer(self.allocator).print("{s}: {s}", .{ param.name, param_type });
}
try self.output.writer(self.allocator).print(") callconv(.C) {s};\n\n", .{return_type});
}
fn writeCTypeAlias(self: *CodeGen, alias: patterns.CTypeAlias) !void {
if (alias.doc_comment) |doc| try self.writeDocComment(doc);
const zig_name = naming.typeNameToZig(alias.name);
try self.output.writer(self.allocator).print("pub const {s} = c.{s};\n\n", .{ zig_name, alias.name });
}
fn enumShouldBeFlags(self: *CodeGen, enum_decl: EnumDecl) bool {
_ = self;
for (enum_decl.values) |value| {
if (value.value) |val| {
if (std.mem.indexOf(u8, val, "|") != null) return true;
}
}
return false;
}
fn convertEnumToFlags(self: *CodeGen, enum_decl: EnumDecl) !FlagDecl {
var flags_list: std.ArrayList(patterns.FlagValue) = .empty;
errdefer flags_list.deinit(self.allocator);
var implicit_value: u32 = 0;
for (enum_decl.values) |value| {
const current_value: ?u32 = if (value.value) |val| blk: {
const trimmed = std.mem.trim(u8, val, " \t");
if (std.mem.indexOf(u8, trimmed, "|") != null) break :blk null;
const parsed = std.fmt.parseInt(u32, trimmed, 0) catch break :blk null;
implicit_value = parsed + 1;
break :blk parsed;
} else blk: {
const val = implicit_value;
implicit_value += 1;
break :blk val;
};
if (current_value) |val| {
if (val == 0) continue;
if (val > 0 and (val & (val - 1)) == 0) {
try flags_list.append(self.allocator, .{
.name = try self.allocator.dupe(u8, value.name),
.value = try std.fmt.allocPrint(self.allocator, "(1u << {d})", .{@ctz(val)}),
.comment = if (value.comment) |c| try self.allocator.dupe(u8, c) else null,
});
}
}
}
return .{
.name = try self.allocator.dupe(u8, enum_decl.name),
.underlying_type = try self.allocator.dupe(u8, "Uint32"),
.flags = try flags_list.toOwnedSlice(self.allocator),
.constants = &.{},
.doc_comment = if (enum_decl.doc_comment) |doc| try self.allocator.dupe(u8, doc) else null,
};
}
fn writeEnum(self: *CodeGen, enum_decl: EnumDecl) !void {
if (enum_decl.values.len == 0) return;
if (self.enumShouldBeFlags(enum_decl)) {
const flag_decl = try self.convertEnumToFlags(enum_decl);
defer flag_decl.deinit(self.allocator);
return self.writeFlags(flag_decl);
}
const zig_name = naming.typeNameToZig(enum_decl.name);
if (enum_decl.doc_comment) |doc| try self.writeDocComment(doc);
try self.output.writer(self.allocator).print("pub const {s} = enum(c_int) {{\n", .{zig_name});
const value_names = try self.allocator.alloc([]const u8, enum_decl.values.len);
defer self.allocator.free(value_names);
for (enum_decl.values, 0..) |value, i| value_names[i] = value.name;
const prefix = try naming.detectCommonPrefix(value_names, self.allocator);
defer self.allocator.free(prefix);
var identifier_to_value = std.StringHashMap([]const u8).init(self.allocator);
defer identifier_to_value.deinit();
var value_to_name = std.StringHashMap([]const u8).init(self.allocator);
defer value_to_name.deinit();
for (enum_decl.values) |value| {
if (value.value) |explicit_value| {
const clean_value = if (std.mem.endsWith(u8, explicit_value, "u")) explicit_value[0 .. explicit_value.len - 1] else explicit_value;
try identifier_to_value.put(value.name, clean_value);
}
}
const resolved_values = try self.allocator.alloc(?[]const u8, enum_decl.values.len);
defer self.allocator.free(resolved_values);
for (enum_decl.values, 0..) |value, i| {
if (value.value) |explicit_value| {
const clean_value = if (std.mem.endsWith(u8, explicit_value, "u")) explicit_value[0 .. explicit_value.len - 1] else explicit_value;
if (clean_value.len > 0 and !std.ascii.isDigit(clean_value[0])) {
resolved_values[i] = identifier_to_value.get(clean_value) orelse clean_value;
} else {
resolved_values[i] = clean_value;
}
} else {
resolved_values[i] = null;
}
}
const EnumAlias = struct { name: []const u8, target: []const u8, comment: ?[]const u8 };
var aliases: std.ArrayList(EnumAlias) = .empty;
defer aliases.deinit(self.allocator);
for (enum_decl.values, 0..) |value, i| {
const zig_value = try naming.enumValueToZig(value.name, prefix, self.allocator);
defer self.allocator.free(zig_value);
if (resolved_values[i]) |numeric_value| {
if (value_to_name.get(numeric_value)) |first_name| {
try aliases.append(self.allocator, .{
.name = try self.allocator.dupe(u8, zig_value),
.target = try naming.enumValueToZig(first_name, prefix, self.allocator),
.comment = if (value.comment) |c| try self.allocator.dupe(u8, c) else null,
});
} else {
try value_to_name.put(numeric_value, value.name);
if (value.comment) |comment| {
try self.output.writer(self.allocator).print(" {s} = {s}, //{s}\n", .{ zig_value, numeric_value, comment });
} else {
try self.output.writer(self.allocator).print(" {s} = {s},\n", .{ zig_value, numeric_value });
}
}
} else if (value.comment) |comment| {
try self.output.writer(self.allocator).print(" {s}, //{s}\n", .{ zig_value, comment });
} else {
try self.output.writer(self.allocator).print(" {s},\n", .{zig_value});
}
}
if (aliases.items.len > 0) {
try self.output.appendSlice(self.allocator, "\n");
for (aliases.items) |alias_decl| {
if (alias_decl.comment) |comment| {
try self.output.writer(self.allocator).print(" pub const {s} = .{s}; //{s}\n", .{ alias_decl.name, alias_decl.target, comment });
} else {
try self.output.writer(self.allocator).print(" pub const {s} = .{s};\n", .{ alias_decl.name, alias_decl.target });
}
self.allocator.free(alias_decl.name);
self.allocator.free(alias_decl.target);
if (alias_decl.comment) |comment| self.allocator.free(comment);
}
}
try self.output.appendSlice(self.allocator, "};\n\n");
}
fn writeStruct(self: *CodeGen, struct_decl: StructDecl) !void {
const zig_name = naming.typeNameToZig(struct_decl.name);
if (struct_decl.doc_comment) |doc| try self.writeDocComment(doc);
if (struct_decl.has_unions) {
try self.output.writer(self.allocator).print("pub const {s} = opaque {{}};\n\n", .{zig_name});
return;
}
try self.output.writer(self.allocator).print("pub const {s} = extern struct {{\n", .{zig_name});
for (struct_decl.fields) |field| {
const zig_type = try self.convertAggregateFieldType(field.type_name);
defer self.allocator.free(zig_type);
if (field.comment) |comment| {
try self.output.writer(self.allocator).print(" {s}: {s}, // {s}\n", .{ field.name, zig_type, comment });
} else {
try self.output.writer(self.allocator).print(" {s}: {s},\n", .{ field.name, zig_type });
}
}
try self.output.appendSlice(self.allocator, "};\n\n");
}
fn writeUnion(self: *CodeGen, union_decl: patterns.UnionDecl) !void {
const zig_name = naming.typeNameToZig(union_decl.name);
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});
for (union_decl.fields) |field| {
const zig_type = try self.convertAggregateFieldType(field.type_name);
defer self.allocator.free(zig_type);
if (field.comment) |comment| {
try self.output.writer(self.allocator).print(" {s}: {s}, // {s}\n", .{ field.name, zig_type, comment });
} else {
try self.output.writer(self.allocator).print(" {s}: {s},\n", .{ field.name, zig_type });
}
}
try self.output.appendSlice(self.allocator, "};\n\n");
}
fn writeFlags(self: *CodeGen, flag_decl: FlagDecl) !void {
const zig_name = naming.typeNameToZig(flag_decl.name);
if (flag_decl.doc_comment) |doc| try self.writeDocComment(doc);
const underlying_type = if (std.mem.eql(u8, flag_decl.underlying_type, "Uint8"))
"u8"
else if (std.mem.eql(u8, flag_decl.underlying_type, "Uint16"))
"u16"
else if (std.mem.eql(u8, flag_decl.underlying_type, "Uint64"))
"u64"
else
"u32";
const type_bits: u32 = if (std.mem.eql(u8, underlying_type, "u8"))
8
else if (std.mem.eql(u8, underlying_type, "u16"))
16
else if (std.mem.eql(u8, underlying_type, "u64"))
64
else
32;
try self.output.writer(self.allocator).print("pub const {s} = packed struct({s}) {{\n", .{ zig_name, underlying_type });
const flag_names = try self.allocator.alloc([]const u8, flag_decl.flags.len);
defer self.allocator.free(flag_names);
for (flag_decl.flags, 0..) |flag, i| flag_names[i] = flag.name;
const prefix = try naming.detectCommonPrefix(flag_names, self.allocator);
defer self.allocator.free(prefix);
var used_bits = std.bit_set.IntegerBitSet(64).initEmpty();
for (flag_decl.flags) |flag| {
const zig_flag = try naming.flagNameToZig(flag.name, prefix, self.allocator);
defer self.allocator.free(zig_flag);
const bit_pos = self.parseBitPosition(flag.value) catch continue;
used_bits.set(bit_pos);
if (flag.comment) |comment| {
try self.output.writer(self.allocator).print(" {s}: bool = false, // {s}\n", .{ zig_flag, comment });
} else {
try self.output.writer(self.allocator).print(" {s}: bool = false,\n", .{zig_flag});
}
}
const padding_bits = type_bits - used_bits.count() - 1;
if (padding_bits > 0) {
try self.output.writer(self.allocator).print(" pad0: u{d} = 0,\n", .{padding_bits});
}
try self.output.appendSlice(self.allocator, " rsvd: bool = false,\n\n");
try self.output.writer(self.allocator).print(" pub const None = {s}{{}};\n", .{zig_name});
for (flag_decl.constants) |constant| {
const zig_const_camel = try naming.flagNameToZig(constant.name, prefix, self.allocator);
defer self.allocator.free(zig_const_camel);
const zig_const = try self.allocator.dupe(u8, zig_const_camel);
defer self.allocator.free(zig_const);
if (zig_const.len > 0) zig_const[0] = std.ascii.toUpper(zig_const[0]);
if (constant.comment) |comment| {
try self.output.writer(self.allocator).print(" pub const {s}: {s} = @bitCast(@as({s}, {s})); // {s}\n", .{ zig_const, zig_name, underlying_type, constant.value, comment });
} else {
try self.output.writer(self.allocator).print(" pub const {s}: {s} = @bitCast(@as({s}, {s}));\n", .{ zig_const, zig_name, underlying_type, constant.value });
}
}
try self.output.appendSlice(self.allocator, "};\n\n");
}
fn writeFunctionAsMethod(self: *CodeGen, func: patterns.FunctionDecl, owner_type: []const u8) !void {
const zig_name = try naming.functionNameToZig(func.name, self.allocator);
defer self.allocator.free(zig_name);
if (func.doc_comment) |doc| try self.writeDocComment(doc);
const zig_return_type = try types.convertType(func.return_type, self.allocator);
defer self.allocator.free(zig_return_type);
try self.output.writer(self.allocator).print(" pub inline fn {s}(", .{zig_name});
for (func.params, 0..) |param, i| {
const zig_type = try self.convertFunctionParamType(func, param);
defer self.allocator.free(zig_type);
if (i > 0) try self.output.appendSlice(self.allocator, ", ");
if (i == 0) {
const lower_name = try std.ascii.allocLowerString(self.allocator, owner_type);
defer self.allocator.free(lower_name);
const non_nullable = if (std.mem.startsWith(u8, zig_type, "?*")) zig_type[1..] else zig_type;
try self.output.writer(self.allocator).print("{s}: {s}", .{ lower_name, non_nullable });
} else if (param.name.len > 0) {
try self.output.writer(self.allocator).print("{s}: {s}", .{ param.name, zig_type });
} else {
try self.output.writer(self.allocator).print("{s}", .{zig_type});
}
}
try self.output.writer(self.allocator).print(") {s} {{\n", .{zig_return_type});
try self.output.appendSlice(self.allocator, " return ");
const return_cast = if (!std.mem.eql(u8, zig_return_type, "void")) types.getReturnCastType(zig_return_type) else .none;
if (return_cast != .none) {
try self.output.writer(self.allocator).print("{s}(", .{castTypeToString(return_cast)});
}
try self.output.writer(self.allocator).print("c.{s}(", .{func.name});
for (func.params, 0..) |param, i| {
if (i > 0) try self.output.appendSlice(self.allocator, ", ");
if (param.name.len > 0 or i == 0) {
const param_name = if (i == 0) blk: {
const lower = try std.ascii.allocLowerString(self.allocator, owner_type);
defer self.allocator.free(lower);
break :blk try self.allocator.dupe(u8, lower);
} else try self.allocator.dupe(u8, param.name);
defer self.allocator.free(param_name);
const zig_param_type = try self.convertFunctionParamType(func, param);
defer self.allocator.free(zig_param_type);
const param_cast = self.getFunctionParamCastType(param, zig_param_type);
if (param_cast == .none) {
try self.output.writer(self.allocator).print("{s}", .{param_name});
} else {
try self.output.writer(self.allocator).print("{s}({s})", .{ castTypeToString(param_cast), param_name });
}
}
}
if (return_cast != .none) {
try self.output.appendSlice(self.allocator, "));\n");
} else {
try self.output.appendSlice(self.allocator, ");\n");
}
try self.output.appendSlice(self.allocator, " }\n\n");
}
fn writeFunction(self: *CodeGen, func: patterns.FunctionDecl) !void {
const zig_name = try naming.functionNameToZig(func.name, self.allocator);
defer self.allocator.free(zig_name);
if (func.doc_comment) |doc| try self.writeDocComment(doc);
const zig_return_type = try types.convertType(func.return_type, self.allocator);
defer self.allocator.free(zig_return_type);
try self.output.writer(self.allocator).print("pub inline fn {s}(", .{zig_name});
for (func.params, 0..) |param, i| {
const zig_type = try self.convertFunctionParamType(func, param);
defer self.allocator.free(zig_type);
if (i > 0) try self.output.appendSlice(self.allocator, ", ");
if (param.name.len > 0) {
try self.output.writer(self.allocator).print("{s}: {s}", .{ param.name, zig_type });
} else {
try self.output.writer(self.allocator).print("{s}", .{zig_type});
}
}
try self.output.writer(self.allocator).print(") {s} {{\n", .{zig_return_type});
try self.output.appendSlice(self.allocator, " return ");
const return_cast = if (!std.mem.eql(u8, zig_return_type, "void")) types.getReturnCastType(zig_return_type) else .none;
if (return_cast != .none) {
try self.output.writer(self.allocator).print("{s}(", .{castTypeToString(return_cast)});
}
try self.output.writer(self.allocator).print("c.{s}(", .{func.name});
for (func.params, 0..) |param, i| {
if (i > 0) try self.output.appendSlice(self.allocator, ", ");
if (param.name.len > 0) {
const zig_param_type = try self.convertFunctionParamType(func, param);
defer self.allocator.free(zig_param_type);
const param_cast = self.getFunctionParamCastType(param, zig_param_type);
if (param_cast == .none) {
try self.output.writer(self.allocator).print("{s}", .{param.name});
} else {
try self.output.writer(self.allocator).print("{s}({s})", .{ castTypeToString(param_cast), param.name });
}
}
}
if (return_cast != .none) {
try self.output.appendSlice(self.allocator, "));\n");
} else {
try self.output.appendSlice(self.allocator, ");\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 {
return switch (cast_type) {
.none => "none",
.ptr_cast => "@ptrCast",
.bit_cast => "@bitCast",
.int_from_enum => "@intFromEnum",
.enum_from_int => "@enumFromInt",
};
}
fn writeDocComment(self: *CodeGen, comment: []const u8) !void {
_ = self;
_ = comment;
}
fn parseBitPosition(self: *CodeGen, value: []const u8) !u6 {
_ = self;
var trimmed = std.mem.trim(u8, value, " \t()");
if (std.mem.startsWith(u8, trimmed, "SDL_UINT64_C(")) {
trimmed = std.mem.trim(u8, trimmed["SDL_UINT64_C(".len..], " \t)");
}
if (trimmed.len > 0 and (trimmed[trimmed.len - 1] == 'u' or trimmed[trimmed.len - 1] == 'U')) {
trimmed = trimmed[0 .. trimmed.len - 1];
}
if (std.mem.indexOf(u8, trimmed, "<<")) |shift_pos| {
const after_shift = std.mem.trim(u8, trimmed[shift_pos + 2 ..], " \t)");
return try std.fmt.parseInt(u6, after_shift, 10);
}
if (std.mem.startsWith(u8, trimmed, "0x")) {
const val = try std.fmt.parseInt(u64, trimmed[2..], 16);
var bit: u7 = 0;
while (bit < 64) : (bit += 1) {
if (val == (@as(u64, 1) << @as(u6, @intCast(bit)))) return @intCast(bit);
}
}
if (std.fmt.parseInt(u64, trimmed, 10)) |val| {
if (val == 0) return error.InvalidBitPosition;
var bit: u7 = 0;
while (bit < 64) : (bit += 1) {
if (val == (@as(u64, 1) << @as(u6, @intCast(bit)))) return @intCast(bit);
}
return error.InvalidBitPosition;
} else |_| {}
return error.InvalidBitPosition;
}
};
test "generated module aliases foreign shared types" {
const allocator = std.testing.allocator;
var decls: std.ArrayList(ResolvedDecl) = .empty;
defer {
for (decls.items) |decl| decl.deinit(allocator);
decls.deinit(allocator);
}
const method_params = try allocator.alloc(patterns.ParamDecl, 2);
method_params[0] = .{ .name = try allocator.dupe(u8, "device"), .type_name = try allocator.dupe(u8, "SDL_GPUDevice *") };
method_params[1] = .{ .name = try allocator.dupe(u8, "window"), .type_name = try allocator.dupe(u8, "SDL_Window *") };
try decls.append(allocator, .{
.decl = .{ .opaque_type = .{ .name = try allocator.dupe(u8, "SDL_GPUDevice"), .doc_comment = null } },
.source_header = try allocator.dupe(u8, "SDL_gpu.h"),
.is_primary_header_decl = true,
});
try decls.append(allocator, .{
.decl = .{ .function_decl = .{
.name = try allocator.dupe(u8, "SDL_WaitForWindow"),
.return_type = try allocator.dupe(u8, "void"),
.params = method_params,
.doc_comment = null,
} },
.source_header = try allocator.dupe(u8, "SDL_gpu.h"),
.is_primary_header_decl = true,
});
try decls.append(allocator, .{
.decl = .{ .opaque_type = .{ .name = try allocator.dupe(u8, "SDL_Window"), .doc_comment = null } },
.source_header = try allocator.dupe(u8, "SDL_video.h"),
.is_primary_header_decl = false,
});
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, "const video_api = @import(\"video.zig\");") != null);
try std.testing.expect(std.mem.indexOf(u8, output, "pub const Window = video_api.Window;") != null);
try std.testing.expect(std.mem.indexOf(u8, output, "pub const c = @import(\"../c.zig\").c;") != null);
try std.testing.expect(std.mem.indexOf(u8, output, "pub const Window = opaque") == null);
}
test "primary declarations are emitted without self imports" {
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_Window"), .doc_comment = null } },
.source_header = try allocator.dupe(u8, "SDL_video.h"),
.is_primary_header_decl = true,
});
const output = try CodeGen.generate(allocator, .{
.decls = decls.items,
.module_name = "video",
});
defer allocator.free(output);
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);
}
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);
}