const std = @import("std"); const reg = @import("registry.zig"); const xml = @import("../xml.zig"); const renderRegistry = @import("render.zig").render; const parseXml = @import("parse.zig").parseXml; const IdRenderer = @import("../id_render.zig").IdRenderer; const mem = std.mem; const Allocator = mem.Allocator; const FeatureLevel = reg.FeatureLevel; const EnumFieldMerger = struct { const EnumExtensionMap = std.StringArrayHashMap(std.ArrayListUnmanaged(reg.Enum.Field)); const FieldSet = std.StringArrayHashMap(void); gpa: *Allocator, reg_arena: *Allocator, registry: *reg.Registry, enum_extensions: EnumExtensionMap, field_set: FieldSet, fn init(gpa: *Allocator, reg_arena: *Allocator, registry: *reg.Registry) EnumFieldMerger { return .{ .gpa = gpa, .reg_arena = reg_arena, .registry = registry, .enum_extensions = EnumExtensionMap.init(gpa), .field_set = FieldSet.init(gpa), }; } fn deinit(self: *EnumFieldMerger) void { for (self.enum_extensions.items()) |*entry| { entry.value.deinit(self.gpa); } self.field_set.deinit(); self.enum_extensions.deinit(); } fn putEnumExtension(self: *EnumFieldMerger, enum_name: []const u8, field: reg.Enum.Field) !void { const res = try self.enum_extensions.getOrPut(enum_name); if (!res.found_existing) { res.entry.value = std.ArrayListUnmanaged(reg.Enum.Field){}; } try res.entry.value.append(self.gpa, field); } fn addRequires(self: *EnumFieldMerger, reqs: []const reg.Require) !void { for (reqs) |req| { for (req.extends) |enum_ext| { try self.putEnumExtension(enum_ext.extends, enum_ext.field); } } } fn mergeEnumFields(self: *EnumFieldMerger, name: []const u8, base_enum: *reg.Enum) !void { // If there are no extensions for this enum, assume its valid. const extensions = self.enum_extensions.get(name) orelse return; self.field_set.clearRetainingCapacity(); const n_fields_upper_bound = base_enum.fields.len + extensions.items.len; const new_fields = try self.reg_arena.alloc(reg.Enum.Field, n_fields_upper_bound); var i: usize = 0; for (base_enum.fields) |field| { const res = try self.field_set.getOrPut(field.name); if (!res.found_existing) { new_fields[i] = field; i += 1; } } // Assume that if a field name clobbers, the value is the same for (extensions.items) |field| { const res = try self.field_set.getOrPut(field.name); if (!res.found_existing) { new_fields[i] = field; i += 1; } } // Existing base_enum.fields was allocatued by `self.reg_arena`, so // it gets cleaned up whenever that is deinited. base_enum.fields = self.reg_arena.shrink(new_fields, i); } fn merge(self: *EnumFieldMerger) !void { for (self.registry.features) |feature| { try self.addRequires(feature.requires); } for (self.registry.extensions) |ext| { try self.addRequires(ext.requires); } // Merge all the enum fields. // Assume that all keys of enum_extensions appear in `self.registry.decls` for (self.registry.decls) |*decl| { if (decl.decl_type == .enumeration) { try self.mergeEnumFields(decl.name, &decl.decl_type.enumeration); } } } }; pub const Generator = struct { gpa: *Allocator, reg_arena: std.heap.ArenaAllocator, registry: reg.Registry, id_renderer: IdRenderer, fn init(allocator: *Allocator, spec: *xml.Element) !Generator { const result = try parseXml(allocator, spec); const tags = try allocator.alloc([]const u8, result.registry.tags.len); for (tags) |*tag, i| tag.* = result.registry.tags[i].name; return Generator{ .gpa = allocator, .reg_arena = result.arena, .registry = result.registry, .id_renderer = IdRenderer.init(allocator, tags), }; } fn deinit(self: Generator) void { self.gpa.free(self.id_renderer.tags); self.reg_arena.deinit(); } fn stripFlagBits(self: Generator, name: []const u8) []const u8 { const tagless = self.id_renderer.stripAuthorTag(name); return tagless[0 .. tagless.len - "FlagBits".len]; } fn stripFlags(self: Generator, name: []const u8) []const u8 { const tagless = self.id_renderer.stripAuthorTag(name); return tagless[0 .. tagless.len - "Flags".len]; } // Solve `registry.declarations` according to `registry.extensions` and `registry.features`. fn mergeEnumFields(self: *Generator) !void { var merger = EnumFieldMerger.init(self.gpa, &self.reg_arena.allocator, &self.registry); defer merger.deinit(); try merger.merge(); } fn fixupTags(self: *Generator) !void { var fixer_upper = TagFixerUpper.init(self.gpa, &self.registry, &self.id_renderer); defer fixer_upper.deinit(); try fixer_upper.fixup(); } fn render(self: *Generator, writer: anytype) !void { try renderRegistry(writer, &self.reg_arena.allocator, &self.registry, &self.id_renderer); } }; /// Main function for generating the Vulkan bindings. vk.xml is to be provided via `spec_xml`, /// and the resulting binding is written to `writer`. `allocator` will be used to allocate temporary /// internal datastructures - mostly via an ArenaAllocator, but sometimes a hashmap uses this allocator /// directly. pub fn generate(allocator: *Allocator, spec_xml: []const u8, writer: anytype) !void { const spec = try xml.parse(allocator, spec_xml); defer spec.deinit(); var gen = try Generator.init(allocator, spec.root); defer gen.deinit(); try gen.mergeEnumFields(); try gen.render(writer); }