Backlog/lib/sdl3/parser/codegen.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"));
}