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 { 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")); }