398 lines
14 KiB
Zig
398 lines
14 KiB
Zig
const std = @import("std");
|
|
const Allocator = std.mem.Allocator;
|
|
const patterns = @import("patterns.zig");
|
|
const naming = @import("naming.zig");
|
|
const types = @import("types.zig");
|
|
|
|
const Declaration = patterns.Declaration;
|
|
const OpaqueType = patterns.OpaqueType;
|
|
const EnumDecl = patterns.EnumDecl;
|
|
const StructDecl = patterns.StructDecl;
|
|
const FlagDecl = patterns.FlagDecl;
|
|
|
|
pub const CodeGen = struct {
|
|
decls: []Declaration,
|
|
allocator: Allocator,
|
|
output: std.ArrayList(u8),
|
|
|
|
pub fn generate(allocator: Allocator, decls: []Declaration) ![]const u8 {
|
|
var gen = CodeGen{
|
|
.decls = decls,
|
|
.allocator = allocator,
|
|
.output = try std.ArrayList(u8).initCapacity(allocator, 4096),
|
|
};
|
|
|
|
try gen.writeHeader();
|
|
try gen.writeDeclarations();
|
|
|
|
return try gen.output.toOwnedSlice(allocator);
|
|
}
|
|
|
|
fn writeHeader(self: *CodeGen) !void {
|
|
const header =
|
|
\\pub const c = @import("c.zig").c;
|
|
\\
|
|
\\
|
|
;
|
|
try self.output.appendSlice(self.allocator, header);
|
|
}
|
|
|
|
fn writeDeclarations(self: *CodeGen) !void {
|
|
// Generate each declaration
|
|
for (self.decls) |decl| {
|
|
switch (decl) {
|
|
.opaque_type => |opaque_decl| try self.writeOpaque(opaque_decl),
|
|
.enum_decl => |enum_decl| try self.writeEnum(enum_decl),
|
|
.struct_decl => |struct_decl| try self.writeStruct(struct_decl),
|
|
.flag_decl => |flag_decl| try self.writeFlags(flag_decl),
|
|
.function_decl => |func| try self.writeFunction(func),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn writeOpaque(self: *CodeGen, opaque_type: OpaqueType) !void {
|
|
const zig_name = naming.typeNameToZig(opaque_type.name);
|
|
|
|
// Write doc comment if present
|
|
if (opaque_type.doc_comment) |doc| {
|
|
try self.writeDocComment(doc);
|
|
}
|
|
|
|
// pub const GPUDevice = opaque {};
|
|
try self.output.writer(self.allocator).print("pub const {s} = opaque {{}};\n\n", .{zig_name});
|
|
}
|
|
|
|
fn writeEnum(self: *CodeGen, enum_decl: EnumDecl) !void {
|
|
const zig_name = naming.typeNameToZig(enum_decl.name);
|
|
|
|
// Write doc comment if present
|
|
if (enum_decl.doc_comment) |doc| {
|
|
try self.writeDocComment(doc);
|
|
}
|
|
|
|
// pub const GPUPrimitiveType = enum(c_int) {
|
|
try self.output.writer(self.allocator).print("pub const {s} = enum(c_int) {{\n", .{zig_name});
|
|
|
|
// Detect common prefix
|
|
var 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);
|
|
|
|
// Write enum values
|
|
for (enum_decl.values) |value| {
|
|
const zig_value = try naming.enumValueToZig(value.name, prefix, self.allocator);
|
|
defer self.allocator.free(zig_value);
|
|
|
|
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});
|
|
}
|
|
}
|
|
|
|
try self.output.appendSlice(self.allocator, "};\n\n");
|
|
}
|
|
|
|
fn writeStruct(self: *CodeGen, struct_decl: StructDecl) !void {
|
|
const zig_name = naming.typeNameToZig(struct_decl.name);
|
|
|
|
// Write doc comment if present
|
|
if (struct_decl.doc_comment) |doc| {
|
|
try self.writeDocComment(doc);
|
|
}
|
|
|
|
// pub const GPUViewport = extern struct {
|
|
try self.output.writer(self.allocator).print("pub const {s} = extern struct {{\n", .{zig_name});
|
|
|
|
// Write fields
|
|
for (struct_decl.fields) |field| {
|
|
const zig_type = try types.convertType(field.type_name, self.allocator);
|
|
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);
|
|
|
|
// Write doc comment if present
|
|
if (flag_decl.doc_comment) |doc| {
|
|
try self.writeDocComment(doc);
|
|
}
|
|
|
|
// Determine underlying type size (u8, u16, u32, u64)
|
|
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;
|
|
|
|
// pub const GPUTextureUsageFlags = packed struct(u32) {
|
|
try self.output.writer(self.allocator).print("pub const {s} = packed struct({s}) {{\n", .{
|
|
zig_name,
|
|
underlying_type,
|
|
});
|
|
|
|
// Detect common prefix
|
|
var 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);
|
|
|
|
// Track which bits are used
|
|
var used_bits = std.bit_set.IntegerBitSet(64).initEmpty();
|
|
|
|
// Write flag fields
|
|
for (flag_decl.flags) |flag| {
|
|
const zig_flag = try naming.flagNameToZig(flag.name, prefix, self.allocator);
|
|
defer self.allocator.free(zig_flag);
|
|
|
|
// Parse bit position from value like "(1u << 0)"
|
|
const bit_pos = try self.parseBitPosition(flag.value);
|
|
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});
|
|
}
|
|
}
|
|
|
|
// Calculate padding
|
|
const used_count = used_bits.count();
|
|
const padding_bits = type_bits - used_count - 1; // -1 for reserved bit
|
|
|
|
if (padding_bits > 0) {
|
|
try self.output.writer(self.allocator).print(" pad0: u{d} = 0,\n", .{padding_bits});
|
|
}
|
|
|
|
// Always add a reserved bit at the end
|
|
try self.output.appendSlice(self.allocator, " rsvd: bool = false,\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);
|
|
|
|
// Write doc comment if present
|
|
if (func.doc_comment) |doc| {
|
|
try self.writeDocComment(doc);
|
|
}
|
|
|
|
// Convert return type
|
|
const zig_return_type = try types.convertType(func.return_type, self.allocator);
|
|
defer self.allocator.free(zig_return_type);
|
|
|
|
// pub inline fn createGPUDevice(
|
|
try self.output.writer(self.allocator).print("pub inline fn {s}(", .{zig_name});
|
|
|
|
// Write parameters
|
|
for (func.params, 0..) |param, i| {
|
|
const zig_type = try types.convertType(param.type_name, self.allocator);
|
|
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 {
|
|
// Parameter has no name (like void or unnamed param)
|
|
try self.output.writer(self.allocator).print("{s}", .{zig_type});
|
|
}
|
|
}
|
|
|
|
// ) *GPUDevice {
|
|
// Extra trailing comma for zig fmt
|
|
try self.output.writer(self.allocator).print(",) {s} {{\n", .{zig_return_type});
|
|
|
|
// Function body - call C API with appropriate casts
|
|
try self.output.appendSlice(self.allocator, " return ");
|
|
|
|
// Determine if we need a cast
|
|
const needs_cast = !std.mem.eql(u8, zig_return_type, "void");
|
|
const return_cast = if (needs_cast) types.getCastType(zig_return_type) else .none;
|
|
if (return_cast != .none) {
|
|
const cast_str = castTypeToString(return_cast);
|
|
try self.output.writer(self.allocator).print("{s}(", .{cast_str});
|
|
}
|
|
|
|
// c.SDL_FunctionName(
|
|
try self.output.writer(self.allocator).print("c.{s}(", .{func.name});
|
|
|
|
// Pass parameters with casts
|
|
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 types.convertType(param.type_name, self.allocator);
|
|
defer self.allocator.free(zig_param_type);
|
|
|
|
const param_cast = types.getCastType(zig_param_type);
|
|
|
|
if (param_cast == .none) {
|
|
try self.output.writer(self.allocator).print("{s}", .{param.name});
|
|
} else {
|
|
const cast_str = castTypeToString(param_cast);
|
|
try self.output.writer(self.allocator).print("{s}({s})", .{ cast_str, param.name });
|
|
}
|
|
}
|
|
}
|
|
|
|
// Close the call
|
|
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 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 {
|
|
// For now, just skip doc comments
|
|
// TODO: Parse and format doc comments properly
|
|
_ = self;
|
|
_ = comment;
|
|
}
|
|
|
|
fn parseBitPosition(self: *CodeGen, value: []const u8) !u6 {
|
|
_ = self;
|
|
// Parse expressions like "(1u << 0)" or "0x01"
|
|
const trimmed = std.mem.trim(u8, value, " \t()");
|
|
|
|
// Look for bit shift pattern: "1u << N"
|
|
if (std.mem.indexOf(u8, trimmed, "<<")) |shift_pos| {
|
|
const after_shift = std.mem.trim(u8, trimmed[shift_pos + 2 ..], " \t");
|
|
const bit = try std.fmt.parseInt(u6, after_shift, 10);
|
|
return bit;
|
|
}
|
|
|
|
// Hex value like "0x01"
|
|
if (std.mem.startsWith(u8, trimmed, "0x")) {
|
|
const val = try std.fmt.parseInt(u32, trimmed[2..], 16);
|
|
// Find the bit position
|
|
var bit: u6 = 0;
|
|
while (bit < 32) : (bit += 1) {
|
|
if (val == (@as(u32, 1) << @as(u5, @intCast(bit)))) return bit;
|
|
}
|
|
}
|
|
|
|
return error.InvalidBitPosition;
|
|
}
|
|
};
|
|
|
|
test "generate opaque type" {
|
|
const opaque_type = OpaqueType{
|
|
.name = "SDL_GPUDevice",
|
|
.doc_comment = null,
|
|
};
|
|
|
|
var decls = [_]Declaration{.{ .opaque_type = opaque_type }};
|
|
|
|
const output = try CodeGen.generate(std.testing.allocator, decls[0..]);
|
|
defer std.testing.allocator.free(output);
|
|
|
|
const expected =
|
|
\\pub const c = @import("c.zig").c;
|
|
\\
|
|
\\pub const GPUDevice = opaque {};
|
|
\\
|
|
\\
|
|
;
|
|
|
|
try std.testing.expectEqualStrings(expected, output);
|
|
}
|
|
|
|
test "generate enum" {
|
|
var values = [_]patterns.EnumValue{
|
|
.{
|
|
.name = "SDL_GPU_PRIMITIVETYPE_TRIANGLELIST",
|
|
.value = null,
|
|
.comment = " A series of triangles",
|
|
},
|
|
.{
|
|
.name = "SDL_GPU_PRIMITIVETYPE_LINELIST",
|
|
.value = null,
|
|
.comment = " A series of lines",
|
|
},
|
|
};
|
|
|
|
const enum_decl = EnumDecl{
|
|
.name = "SDL_GPUPrimitiveType",
|
|
.values = values[0..],
|
|
.doc_comment = null,
|
|
};
|
|
|
|
var decls = [_]Declaration{.{ .enum_decl = enum_decl }};
|
|
|
|
const output = try CodeGen.generate(std.testing.allocator, decls[0..]);
|
|
defer std.testing.allocator.free(output);
|
|
|
|
// Verify it contains the expected elements
|
|
try std.testing.expect(std.mem.indexOf(u8, output, "pub const GPUPrimitiveType = enum(c_int)") != null);
|
|
try std.testing.expect(std.mem.indexOf(u8, output, "trianglelist") != null);
|
|
try std.testing.expect(std.mem.indexOf(u8, output, "linelist") != null);
|
|
}
|
|
|
|
test "parse bit position" {
|
|
var gen = CodeGen{
|
|
.decls = &[_]Declaration{},
|
|
.allocator = std.testing.allocator,
|
|
.output = try std.ArrayList(u8).initCapacity(std.testing.allocator, 1),
|
|
};
|
|
defer gen.output.deinit(std.testing.allocator);
|
|
|
|
try std.testing.expectEqual(@as(u6, 0), try gen.parseBitPosition("(1u << 0)"));
|
|
try std.testing.expectEqual(@as(u6, 5), try gen.parseBitPosition("1u << 5"));
|
|
try std.testing.expectEqual(@as(u6, 0), try gen.parseBitPosition("0x01"));
|
|
try std.testing.expectEqual(@as(u6, 3), try gen.parseBitPosition("0x08"));
|
|
}
|