From c17d33bb06427d2bd1a53297e86000c0ebbfa062 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Thu, 11 Jun 2020 02:13:12 +0200 Subject: [PATCH] Restructure spec-c-parse to registry/c-parse and spec-parse to registry/parse --- generator/main.zig | 4 +- generator/registry-new.zig | 116 ----- generator/registry.zig | 863 ++++--------------------------------- generator/spec-c-parse.zig | 575 ------------------------ generator/spec-parse.zig | 430 ------------------ 5 files changed, 90 insertions(+), 1898 deletions(-) delete mode 100644 generator/registry-new.zig delete mode 100644 generator/spec-c-parse.zig delete mode 100644 generator/spec-parse.zig diff --git a/generator/main.zig b/generator/main.zig index f9d781f..822471a 100644 --- a/generator/main.zig +++ b/generator/main.zig @@ -1,7 +1,7 @@ const std = @import("std"); const xml = @import("xml.zig"); -const parseXml = @import("spec-parse.zig").parseXml; -const registry = @import("registry-new.zig"); +const parseXml = @import("registry/parse.zig").parseXml; +const registry = @import("registry.zig"); fn renderType(type_info: registry.TypeInfo) void { switch (type_info) { diff --git a/generator/registry-new.zig b/generator/registry-new.zig deleted file mode 100644 index 1e0eb90..0000000 --- a/generator/registry-new.zig +++ /dev/null @@ -1,116 +0,0 @@ -pub const Registry = struct { - decls: []Declaration, - api_constants: []ApiConstant, - tags: []Tag, -}; - -pub const Declaration = struct { - name: []const u8, - decl_type: TypeInfo, -}; - -pub const ApiConstant = struct { - pub const Value = union(enum) { - expr: []const u8, - alias: []const u8, // Alias of another API constant - }; - - name: []const u8, - value: Value, -}; - -pub const Tag = struct { - name: []const u8, - author: []const u8, -}; - -pub const TypeInfo = union(enum) { - container: Container, - enumeration: Enum, - bitmask: Bitmask, - handle: Handle, - command: Command, - alias: []const u8, // Alias of another declaration - pointer: Pointer, - array: Array, - opaque: void, - foreign: Foreign -}; - -pub const Container = struct { - pub const Field = struct { - name: []const u8, - field_type: TypeInfo, - bits: ?usize, - }; - - fields: []Field, - is_union: bool, -}; - -pub const Enum = struct { - pub const Value = union(enum) { - bitpos: u5, // 1 << bitpos - bit_vector: i32, // Combined flags & some vendor IDs - int: i32, - alias: struct { - alias_name: []const u8, - is_compat_alias: bool, - } - }; - - pub const Field = struct { - name: []const u8, - value: Value, - }; - - fields: []Field, - is_bitmask: bool, -}; - -pub const Bitmask = struct { - bits_enum: ?[]const u8, -}; - -pub const Handle = struct { - parent: ?[]const u8, // VkInstance has no parent - is_dispatchable: bool, -}; - -pub const Command = struct { - pub const Param = struct { - name: []const u8, - param_type: TypeInfo, - }; - - params: []Param, - return_type: *TypeInfo, - success_codes: []const []const u8, - error_codes: []const []const u8, -}; - -pub const Pointer = struct { - pub const PointerSize = enum { - one, - many, // The length is given by some expression - zero_terminated - }; - - is_const: bool, - size: PointerSize, - child: *TypeInfo, -}; - -pub const Array = struct { - pub const ArraySize = union(enum) { - int: usize, - alias: []const u8, // Field size is given by an api constant - }; - - size: ArraySize, - child: *TypeInfo, -}; - -pub const Foreign = struct { - dependency: []const u8, // Either a header or vk_platform -}; diff --git a/generator/registry.zig b/generator/registry.zig index 0b67995..1e0eb90 100644 --- a/generator/registry.zig +++ b/generator/registry.zig @@ -1,803 +1,116 @@ -const std = @import("std"); -const xml = @import("xml.zig"); -const mem = std.mem; -const Allocator = mem.Allocator; -const SegmentedList = std.SegmentedList; -const StringHashMap = std.StringHashMap; - pub const Registry = struct { - arena: std.heap.ArenaAllocator, - - declarations: SegmentedList(Declaration, 0), - declarations_by_name: StringHashMap(*Declaration), - api_constants: SegmentedList(ApiConstant, 0), - tags: SegmentedList(TagInfo, 0), - extensions: SegmentedList(ExtensionInfo, 0), - - fn init(allocator: *Allocator) !*Registry { - // Use this construction to make sure that the extensions list contains a valid pointer to an allocator - const registry = blk: { - var arena = std.heap.ArenaAllocator.init(allocator); - errdefer arena.deinit(); - - const registry = try arena.allocator.create(Registry); - registry.* = .{ - .arena = arena, - .declarations = undefined, - .declarations_by_name = StringHashMap(*Declaration).init(allocator), - .api_constants = undefined, - .tags = undefined, - .extensions = undefined - }; - - break :blk registry; - }; - - registry.declarations = SegmentedList(Declaration, 0).init(®istry.arena.allocator); - registry.api_constants = SegmentedList(ApiConstant, 0).init(®istry.arena.allocator); - registry.tags = SegmentedList(TagInfo, 0).init(®istry.arena.allocator); - registry.extensions = SegmentedList(ExtensionInfo, 0).init(®istry.arena.allocator); - - return registry; - } - - pub fn fromXml(allocator: *Allocator, root: *xml.Element) *Registry { - std.debug.assert(mem.eql(u8, root.tag, "registry")); - - var registry = Registry.init(allocator) catch unreachable; - - processTypes(registry, root); - processEnums(registry, root); - processCommands(registry, root); - processFeatures(registry, root); - processTags(registry, root); - processExtensions(registry, root); - - return registry; - } - - pub fn deinit(self: Registry) void { - self.declarations_by_name.deinit(); - - // Copy to stack so that the arena doesn't destroy itself - var arena = self.arena; - arena.deinit(); - } - - fn addDefinition(self: *Registry, name: []const u8, definition: Definition) void { - const ptr = self.declarations.addOne() catch unreachable; - ptr.* = .{ - .name = name, - .definition = definition - }; - - if (self.declarations_by_name.put(name, ptr) catch unreachable) |existing| { - std.debug.warn("Duplicate definition {}\n", .{existing.key}); - unreachable; - } - } - - fn addApiConstant(self: *Registry, name: []const u8, expr: []const u8) void { - self.api_constants.push(.{.name = name, .expr = expr}) catch unreachable; - } - - fn addTag(self: *Registry, name: []const u8, author: []const u8) void { - self.tags.push(.{.name = name, .author = author}) catch unreachable; - } - - pub fn findDefinitionByName(self: *Registry, name: []const u8) ?*Definition { - if (self.declarations_by_name.get(name)) |kv| { - return &kv.value.definition; - } - - return null; - } - - pub fn dump(self: *Registry) void { - const indent = " " ** 4; - - { - std.debug.warn("Definitions:\n", .{}); - var it = self.declarations.iterator(0); - while (it.next()) |decl| { - std.debug.warn(indent ++ "{} ({})\n", .{decl.name, std.meta.tagName(decl.definition)}); - } - } - - { - std.debug.warn("API constants:\n", .{}); - var it = self.api_constants.iterator(0); - while (it.next()) |kv| { - std.debug.warn(indent ++ "{} = {}\n", .{kv.name, kv.expr}); - } - } - - { - std.debug.warn("Tags:\n", .{}); - var it = self.tags.iterator(0); - while (it.next()) |tag| { - std.debug.warn(indent ++ "{} ({})\n", .{tag.name, tag.author}); - } - } - - { - std.debug.warn("Extensions:\n", .{}); - var it = self.extensions.iterator(0); - while (it.next()) |ext| { - std.debug.warn(indent ++ "{}: {}, version {}\n", .{ext.number, ext.name, ext.version}); - } - } - } -}; - -pub const ApiConstant = struct { - name: []const u8, - expr: []const u8 -}; - -pub const ExtensionInfo = struct { - name: []const u8, - number: u32, - version: u32, -}; - -pub const TagInfo = struct { - name: []const u8, - author: []const u8 + decls: []Declaration, + api_constants: []ApiConstant, + tags: []Tag, }; pub const Declaration = struct { name: []const u8, - definition: Definition + decl_type: TypeInfo, }; -pub const Definition = union(enum) { - Struct: ContainerInfo, - Union: ContainerInfo, - Enum: EnumInfo, - Bitmask: BitmaskInfo, - Handle: HandleInfo, - FnPtr: CommandInfo, - Command: CommandInfo, - Alias: []const u8, - BaseType: TypeInfo -}; - -pub const HandleInfo = struct { - dispatchable: bool -}; - -pub const BitmaskInfo = struct { - bits_enum: ?[]const u8 -}; - -// Type info of fields, function parameters, and return types. -pub const TypeInfo = struct { - const PointerSize = enum { - One, - Many, // The length is given by some expression which cannot be expressed in Zig - ZeroTerminated - }; - - const Pointer = struct { - is_const: bool, - size: PointerSize +pub const ApiConstant = struct { + pub const Value = union(enum) { + expr: []const u8, + alias: []const u8, // Alias of another API constant }; name: []const u8, - pointers: []Pointer, // Outer-most pointer is the first element - array_size: ?[]const u8, - - fn fromXml(allocator: *Allocator, elem: *xml.Element) TypeInfo { - var type_info = TypeInfo{ - .name = elem.getCharData("type").?, - .pointers = &[_]Pointer{}, - .array_size = elem.getCharData("enum") - }; - - // Find the element which contains the stars of the pointers - var stars: ?[]const u8 = null; - var child_it = elem.children.iterator(0); - while (child_it.next()) |child| { - if (child.* == .CharData and mem.indexOfScalar(u8, child.CharData, '*') != null) { - stars = child.CharData; - break; - } - } - - if (stars) |ptr_text| { - const npointers = count(ptr_text, '*'); - - type_info.pointers = allocator.alloc(TypeInfo.Pointer, npointers) catch unreachable; - - // Read the sizes of each pointer - if (elem.getAttribute("len")) |lens| { - var len_it = std.mem.split(lens, ","); - for (type_info.pointers) |*ptr, i| { - ptr.size = if (len_it.next()) |len| lenToPointerSize(len) else .One; - } - } else { - for (type_info.pointers) |*ptr| { - ptr.size = .One; - } - } - - const pre = switch (elem.children.at(0).*) { - .CharData => |char_data| char_data, - else => "" - }; - - type_info.parseConstness(pre, ptr_text); - } - - return type_info; - } - - fn fromFnPtrReturnTypeXml(allocator: *Allocator, elem: *xml.Element) TypeInfo { - // In function pointers, the return type is not contained within a designated tag. - // The first chardata of the type has the following structure: 'typedef (VKAPI_PTR *' - // In order to parse the , strip everything from it until the last star and take the last word - // to be the type. Then parse everything in front of the return type word and after the return type word - const proto = elem.children.at(0).CharData; - std.debug.assert(mem.startsWith(u8, proto, "typedef ") and mem.endsWith(u8, proto, " (VKAPI_PTR *")); - const return_type = proto["typedef ".len .. proto.len - " (VKAPI_PTR *".len]; - - var first_star = return_type.len; - var npointers: usize = 0; - var i = return_type.len; - while (i > 0) { - i -= 1; - if (return_type[i] == '*') { - first_star = i; - npointers += 1; - } - } - - const name_start = if (mem.lastIndexOfScalar(u8, return_type[0 .. first_star], ' ')) |index| index + 1 else 0; - var type_info = TypeInfo{ - .name = return_type[name_start .. first_star], - .pointers = &[_]Pointer{}, - .array_size = null - }; - - if (npointers > 0) { - type_info.pointers = allocator.alloc(TypeInfo.Pointer, npointers) catch unreachable; - for (type_info.pointers) |*ptr| { - ptr.size = .One; - } - - type_info.parseConstness(return_type[0 .. name_start], return_type[first_star ..]); - } - - return type_info; - } - - fn fromFnPtrParamTypeXml(allocator: *Allocator, pre: []const u8, name: []const u8, post: []const u8) TypeInfo { - // `pre` and `post` contain information that is shared with other types, seperated by commas. In - // the case of `pre`, get everything after the comma (if present), and for `post`, everything before - // and including the last star before the last. If there is no star, the segment contains no - // useful information anyway. Note that the star should never appear *after* the comma (that wouldn't be - // a valid C type). - // Ex: void* pUserData, void* pMemory - - const pre_start = if (mem.indexOfScalar(u8, pre, ',')) |index| index + 1 else pre.len; - const post_end = if (mem.indexOfScalar(u8, post, '*')) |index| index + 1 else 0; - const npointers = count(post[0 .. post_end], '*'); - - var type_info = TypeInfo{ - .name = name, - .pointers = &[_]Pointer{}, - .array_size = null - }; - - if (npointers > 0) { - type_info.pointers = allocator.alloc(TypeInfo.Pointer, npointers) catch unreachable; - for (type_info.pointers) |*ptr| { - ptr.size = .One; - } - - type_info.parseConstness(pre[pre_start ..], post[0 .. post_end]); - } - - return type_info; - } - - fn parseConstness(self: *TypeInfo, pre: []const u8, post: []const u8) void { - // Beware: the const of the inner pointer is given before the type name (in `pre`) - // while the others are in the `post`. - - // Check the outer pointers - var const_it = std.mem.split(post, "*"); - var i: usize = self.pointers.len; - while (i > 0) { - i -= 1; - const is_const = mem.indexOf(u8, const_it.next().?, "const") != null; - self.pointers[i].is_const = is_const; - } - - // Check the inner-most pointer - const first_const = mem.indexOf(u8, pre, "const") != null; - self.pointers[self.pointers.len - 1].is_const = first_const; - } - - pub fn format( - self: TypeInfo, - comptime fmt: []const u8, - options: std.fmt.FormatOptions, - context: var, - comptime Errors: type, - output: fn (@TypeOf(context), []const u8) Errors!void - ) Errors!void { - if (self.array_size) |array_size| { - try std.fmt.format(context, Errors, output, "[{}]", .{array_size}); - } - - for (self.pointers) |ptr| { - switch (ptr.size) { - .One => try output(context, "*"), - .Many => try output(context, "[*]"), - .ZeroTerminated => try output(context, "[*:0]") - } - - if (ptr.is_const) { - try output(context, "const "); - } - } - - if (self.name) |name| { - try output(context, self.name); - } else { - try output(context, "@Type(.Opaque)"); - } - } - - fn lenToPointerSize(len: []const u8) PointerSize { - if (mem.eql(u8, len, "null-terminated")) { - return .ZeroTerminated; - } else if (mem.eql(u8, len, "1")) { - return .One; - } else { - return .Many; - } - } + value: Value, }; -pub const ContainerInfo = struct { - const Member = struct { - name: []const u8, - type_info: TypeInfo - }; - - members: SegmentedList(Member, 0), - - fn init(allocator: *Allocator) ContainerInfo { - return .{ - .members = SegmentedList(Member, 0).init(allocator) - }; - } - - fn fromXml(allocator: *Allocator, elem: *xml.Element) ContainerInfo { - var s = ContainerInfo.init(allocator); - - var members = elem.findChildrenByTag("member"); - while (members.next()) |member| { - const member_name = member.getCharData("name").?; - const type_info = TypeInfo.fromXml(allocator, member); - - s.addMember(member_name, type_info); - } - - return s; - } - - fn addMember(self: *ContainerInfo, name: []const u8, type_info: TypeInfo) void { - self.members.push(.{.name = name, .type_info = type_info}) catch unreachable; - } +pub const Tag = struct { + name: []const u8, + author: []const u8, }; -pub const CommandInfo = struct { - const Parameter = struct { +pub const TypeInfo = union(enum) { + container: Container, + enumeration: Enum, + bitmask: Bitmask, + handle: Handle, + command: Command, + alias: []const u8, // Alias of another declaration + pointer: Pointer, + array: Array, + opaque: void, + foreign: Foreign +}; + +pub const Container = struct { + pub const Field = struct { name: []const u8, - type_info: TypeInfo + field_type: TypeInfo, + bits: ?usize, }; - parameters: SegmentedList(Parameter, 0), - return_type_info: TypeInfo, + fields: []Field, + is_union: bool, +}; + +pub const Enum = struct { + pub const Value = union(enum) { + bitpos: u5, // 1 << bitpos + bit_vector: i32, // Combined flags & some vendor IDs + int: i32, + alias: struct { + alias_name: []const u8, + is_compat_alias: bool, + } + }; + + pub const Field = struct { + name: []const u8, + value: Value, + }; + + fields: []Field, + is_bitmask: bool, +}; + +pub const Bitmask = struct { + bits_enum: ?[]const u8, +}; + +pub const Handle = struct { + parent: ?[]const u8, // VkInstance has no parent + is_dispatchable: bool, +}; + +pub const Command = struct { + pub const Param = struct { + name: []const u8, + param_type: TypeInfo, + }; + + params: []Param, + return_type: *TypeInfo, success_codes: []const []const u8, error_codes: []const []const u8, - - fn init(allocator: *Allocator, return_type_info: TypeInfo) CommandInfo { - return .{ - .parameters = SegmentedList(Parameter, 0).init(allocator), - .return_type_info = return_type_info, - .success_codes = &[_][]u8{}, - .error_codes = &[_][]u8{}, - }; - } - - fn fromXml(allocator: *Allocator, elem: *xml.Element) CommandInfo { - const return_type_info = TypeInfo.fromXml(allocator, elem.findChildByTag("proto").?); - var cmd = CommandInfo.init(allocator, return_type_info); - - if (elem.getAttribute("successcodes")) |codes| { - cmd.success_codes = CommandInfo.splitResultCodes(allocator, codes); - } - - if (elem.getAttribute("errorcodes")) |codes| { - cmd.error_codes = CommandInfo.splitResultCodes(allocator, codes); - } - - var parameters = elem.findChildrenByTag("param"); - while (parameters.next()) |param| { - const param_name = param.getCharData("name").?; - const type_info = TypeInfo.fromXml(allocator, param); - - cmd.addParameter(param_name, type_info); - } - - return cmd; - } - - fn fromFnPtrXml(allocator: *Allocator, elem: *xml.Element) CommandInfo { - const return_type_info = TypeInfo.fromFnPtrReturnTypeXml(allocator, elem); - var cmd = CommandInfo.init(allocator, return_type_info); - - // The parameters of a function pointer are formulated a bit weird, which is why - // the chardata surrounding a is also required to parse it completely. - // This loop assumes there are no other elements in a function pointers declatation. - var i: usize = 3; // The first parameter's type is at offset 3 - while (i < elem.children.count() - 1) : (i += 2) { - const pre = elem.children.at(i - 1).CharData; - const type_name = elem.children.at(i).Element.children.at(0).CharData; - const post = elem.children.at(i + 1).CharData; - const type_info = TypeInfo.fromFnPtrParamTypeXml(allocator, pre, type_name, post); - - // To find the type name, take everything until the first space before the last ) or ,. - const name_end = mem.lastIndexOfAny(u8, post, "),").?; - const name_start = mem.lastIndexOfScalar(u8, post[0 .. name_end], ' ').? + 1; - const name = post[name_start .. name_end]; - - cmd.addParameter(name, type_info); - } - - return cmd; - } - - fn splitResultCodes(allocator: *Allocator, text: []const u8) []const []const u8 { - const ncodes = 1 + count(text, ','); - const codes = allocator.alloc([]const u8, ncodes) catch unreachable; - - var it = mem.split(text, ","); - - for (codes) |*code, i| { - code.* = it.next().?; - } - - return codes; - } - - fn addParameter(self: *CommandInfo, name: []const u8, type_info: TypeInfo) void { - self.parameters.push(.{.name = name, .type_info = type_info}) catch unreachable; - } }; -pub const EnumInfo = struct { - const Value = union(enum) { - Bitpos: u5, //log2(u32) - HexValue: i32, // Combined flags and such - Value: i32, - Alias: []const u8, +pub const Pointer = struct { + pub const PointerSize = enum { + one, + many, // The length is given by some expression + zero_terminated }; - const Variant = struct { - name: []const u8, - value: Value - }; - - variants: SegmentedList(Variant, 0), - - fn init(allocator: *Allocator) EnumInfo { - return .{ - .variants = SegmentedList(Variant, 0).init(allocator) - }; - } - - fn addVariant(self: *EnumInfo, name: []const u8, value: Value) void { - // Sometimes a variant is added multiple times with different 'require' parts of an extension - // so filter out any duplicates. - var it = self.variants.iterator(0); - while (it.next()) |variant| { - if (mem.eql(u8, variant.name, name)) { - return; - } - } - - _ = self.variants.push(.{.name = name, .value = value}) catch unreachable; - } - - fn processVariantFromXml(self: *EnumInfo, variant: *xml.Element, ext_nr: ?u32) void { - if (EnumInfo.isBackwardsCompatAlias(variant)) return; - const name = variant.getAttribute("name").?; - const value = blk: { - // An enum variant's value could be defined by any of the following attributes: - // - value: Straight up value of the enum variant, in either base 10 or 16 (prefixed with 0x). - // - bitpos: Used for bitmasks, and can also be set in extensions. - // - alias: The field is an alias of another variant within the same enum. - // - offset: Used with features and extensions, where a non-bitpos value is added to an enum. - // The value is given by `1e9 + (extr_nr - 1) * 1e3 + offset`, where `ext_nr` is either - // given by the `extnumber` field (in the case of a feature), or given in the parent - // tag. In the latter case its passed via the `ext_nr` parameter. - if (variant.getAttribute("value")) |value_str| { - if (mem.startsWith(u8, value_str, "0x")) { - break :blk Value{.HexValue = std.fmt.parseInt(i32, value_str[2..], 16) catch unreachable}; - } else { - break :blk Value{.Value = std.fmt.parseInt(i32, value_str, 10) catch unreachable}; - } - } else if (variant.getAttribute("bitpos")) |bitpos_str| { - break :blk Value{.Bitpos = std.fmt.parseInt(u5, bitpos_str, 10) catch unreachable}; - } else if (variant.getAttribute("alias")) |alias| { - break :blk Value{.Alias = alias}; - } else if (variant.getAttribute("offset")) |offset_str| { - const offset = std.fmt.parseInt(u32, offset_str, 10) catch unreachable; - - const actual_ext_nr = ext_nr orelse blk: { - const ext_nr_str = variant.getAttribute("extnumber").?; - break :blk std.fmt.parseInt(u32, ext_nr_str, 10) catch unreachable; - }; - - const abs_value = EnumInfo.extensionEnumInfoValue(actual_ext_nr, offset); - const value = if (variant.getAttribute("dir")) |_| -@intCast(i32, abs_value) else @intCast(i32, abs_value); - - break :blk Value{.Value = value}; - } else { - unreachable; - } - }; - - self.addVariant(name, value); - } - - fn isBackwardsCompatAlias(variant: *xml.Element) bool { - if (variant.getAttribute("comment")) |comment| { - return mem.eql(u8, comment, "Backwards-compatible alias containing a typo") or - mem.eql(u8, comment, "Deprecated name for backwards compatibility"); - } - - return false; - } - - fn extensionEnumInfoValue(ext_nr: u32, offset: u32) u32 { - const extension_value_base = 1000000000; - const extension_block = 1000; - return extension_value_base + (ext_nr - 1) * extension_block + offset; - } + is_const: bool, + size: PointerSize, + child: *TypeInfo, }; -fn processTypes(registry: *Registry, root: *xml.Element) void { - var types = root.findChildByTag("types").?; - var it = types.findChildrenByTag("type"); - while (it.next()) |ty| { - const category = ty.getAttribute("category") orelse continue; - if (mem.eql(u8, category, "bitmask")) { - processBitmaskType(registry, ty); - } else if (mem.eql(u8, category, "enum")) { - processEnumType(registry, ty); - } else if (mem.eql(u8, category, "handle")) { - processHandleType(registry, ty); - } else if (mem.eql(u8, category, "struct") or mem.eql(u8, category, "union")) { - processStructType(registry, ty); - } else if (mem.eql(u8, category, "funcpointer")) { - processFuncPointerType(registry, ty); - } else if (mem.eql(u8, category, "basetype")) { - processBaseType(registry, ty); - } else if (mem.eql(u8, category, "define")) { - processDefineType(registry, ty); - } - } -} - -fn processBitmaskType(registry: *Registry, ty: *xml.Element) void { - if (ty.getAttribute("name")) |name| { - const alias = ty.getAttribute("alias").?; - registry.addDefinition(name, .{.Alias = alias}); - } else { - const name = ty.getCharData("name").?; - const info = BitmaskInfo { - .bits_enum = ty.getAttribute("requires") - }; - - registry.addDefinition(name, .{.Bitmask = info}); - } -} - -fn processHandleType(registry: *Registry, ty: *xml.Element) void { - if (ty.getAttribute("alias")) |alias| { - const name = ty.getAttribute("name").?; - registry.addDefinition(name, .{.Alias = alias}); - } else { - const define_type_str = ty.getCharData("type").?; - const name = ty.getCharData("name").?; - const info = HandleInfo { - .dispatchable = std.mem.eql(u8, define_type_str, "VK_DEFINE_HANDLE") - }; - - registry.addDefinition(name, .{.Handle = info}); - } -} - -fn processEnumType(registry: *Registry, ty: *xml.Element) void { - const name = ty.getAttribute("name").?; - const def: Definition = if (ty.getAttribute("alias")) |alias| - .{.Alias = alias} - else - .{.Enum = EnumInfo.init(®istry.arena.allocator)}; - - registry.addDefinition(name, def); -} - -fn processStructType(registry: *Registry, ty: *xml.Element) void { - const name = ty.getAttribute("name").?; - const def = if (ty.getAttribute("alias")) |alias| - Definition{.Alias = alias} - else if (mem.eql(u8, ty.getAttribute("category").?, "union")) - Definition{.Union = ContainerInfo.fromXml(®istry.arena.allocator, ty)} - else - Definition{.Struct = ContainerInfo.fromXml(®istry.arena.allocator, ty)}; - - registry.addDefinition(name, def); -} - -fn processFuncPointerType(registry: *Registry, ty: *xml.Element) void { - const name = ty.getCharData("name").?; - const cmd = CommandInfo.fromFnPtrXml(®istry.arena.allocator, ty); - registry.addDefinition(name, .{.FnPtr = cmd}); -} - -fn processBaseType(registry: *Registry, ty: *xml.Element) void { - const special_names = [_][]const u8{ // handled in render.zig - "ANativeWindow", - "AHardwareBuffer", - "CAMetalLayer" +pub const Array = struct { + pub const ArraySize = union(enum) { + int: usize, + alias: []const u8, // Field size is given by an api constant }; - const name = ty.getCharData("name").?; + size: ArraySize, + child: *TypeInfo, +}; - for (special_names) |special| { - if (mem.eql(u8, name, special)) return; - } - - const type_info = TypeInfo.fromXml(®istry.arena.allocator, ty); - registry.addDefinition(name, .{.BaseType = type_info}); -} - -fn processDefineType(registry: *Registry, ty: *xml.Element) void { - if (ty.getCharData("name")) |name| { - if (mem.eql(u8, name, "VK_HEADER_VERSION")) { - registry.addApiConstant("VK_HEADER_VERSION", mem.trim(u8, ty.children.at(2).CharData, " ")); - } - } -} - -fn processEnums(registry: *Registry, root: *xml.Element) void { - var it = root.findChildrenByTag("enums"); - while (it.next()) |enums| { - const name = enums.getAttribute("name").?; - if (mem.eql(u8, name, "API Constants")) { - processApiConstants(registry, enums); - continue; - } - - // If the declaration hasn't been inserted in processEnumTypes, - // its a bitmask enum that is not used, so ignore it - const def = registry.findDefinitionByName(name) orelse continue; - var enum_it = enums.findChildrenByTag("enum"); - while (enum_it.next()) |variant| { - def.Enum.processVariantFromXml(variant, null); - } - } -} - -fn processApiConstants(registry: *Registry, enums: *xml.Element) void { - var it = enums.findChildrenByTag("enum"); - while (it.next()) |constant| { - const name = constant.getAttribute("name").?; - const expr = constant.getAttribute("value") orelse constant.getAttribute("alias").?; - registry.addApiConstant(name, expr); - } -} - -fn processCommands(registry: *Registry, root: *xml.Element) void { - var commands = root.findChildByTag("commands").?; - var command_it = commands.findChildrenByTag("command"); - while (command_it.next()) |elem| { - if (elem.getAttribute("alias")) |alias| { - const name = elem.getAttribute("name").?; - registry.addDefinition(name, .{.Alias = alias}); - } else { - const name = elem.findChildByTag("proto").?.getCharData("name").?; - const command = CommandInfo.fromXml(®istry.arena.allocator, elem); - registry.addDefinition(name, .{.Command = command}); - } - } -} - -fn processTags(registry: *Registry, root: *xml.Element) void { - var tags = root.findChildByTag("tags").?; - var it = tags.findChildrenByTag("tag"); - while (it.next()) |tag| { - const name = tag.getAttribute("name").?; - const author = tag.getAttribute("author").?; - registry.addTag(name, author); - } -} - -fn processExtensions(registry: *Registry, root: *xml.Element) void { - var extensions = root.findChildByTag("extensions").?; - var ext_it = extensions.findChildrenByTag("extension"); - while (ext_it.next()) |ext| { - if (ext.getAttribute("supported")) |support| { - if (mem.eql(u8, support, "disabled")) continue; - } - - processExtension(registry, ext); - } -} - -fn processExtension(registry: *Registry, ext: *xml.Element) void { - const ext_nr_str = ext.getAttribute("number").?; - const ext_nr = std.fmt.parseInt(u32, ext_nr_str, 10) catch unreachable; - - var version: ?u32 = null; - - var req_it = ext.findChildrenByTag("require"); - while (req_it.next()) |req| { - var it = req.findChildrenByTag("enum"); - while (it.next()) |variant| { - if (variant.getAttribute("extends")) |enum_name| { - // Some extensions define variants for other extensions, - // these are also defined in those extensions, so just skip them - if (variant.getAttribute("extnumber")) |_| continue; - - const def = registry.findDefinitionByName(enum_name).?; - def.Enum.processVariantFromXml(variant, ext_nr); - } else if (variant.getAttribute("name")) |name| { - if (mem.endsWith(u8, name, "_SPEC_VERSION")) { - const version_str = variant.getAttribute("value").?; - version = std.fmt.parseInt(u32, version_str, 10) catch unreachable; - } - } - } - } - - var ext_info = ExtensionInfo{ - .name = ext.getAttribute("name").?, - .number = ext_nr, - .version = version.? - }; - - registry.extensions.push(ext_info) catch unreachable; -} - -fn processFeatures(registry: *Registry, root: *xml.Element) void { - var feature_it = root.findChildrenByTag("feature"); - while (feature_it.next()) |feature| { - var req_it = feature.findChildrenByTag("require"); - while (req_it.next()) |req| { - var enum_it = req.findChildrenByTag("enum"); - while (enum_it.next()) |variant| { - const enum_name = variant.getAttribute("extends") orelse continue; - const def = registry.findDefinitionByName(enum_name).?; - def.Enum.processVariantFromXml(variant, null); - } - } - } -} - -fn count(haystack: []const u8, needle: u8) usize { - var total: usize = 0; - for (haystack) |elem| { - if (elem == needle) total += 1; - } - - return total; -} +pub const Foreign = struct { + dependency: []const u8, // Either a header or vk_platform +}; diff --git a/generator/spec-c-parse.zig b/generator/spec-c-parse.zig deleted file mode 100644 index a33a2b2..0000000 --- a/generator/spec-c-parse.zig +++ /dev/null @@ -1,575 +0,0 @@ -const std = @import("std"); -const registry = @import("registry-new.zig"); -const xml = @import("xml.zig"); -const mem = std.mem; -const Allocator = mem.Allocator; -const testing = std.testing; -const ArraySize = registry.Array.ArraySize; -const TypeInfo = registry.TypeInfo; - -const Token = struct { - id: Id, - text: []const u8, - - const Id = enum { - id, // Any id thats not a keyword - name, // Vulkan ... - type_name, // Vulkan ... - enum_name, // Vulkan ... - int, - star, - comma, - semicolon, - colon, - lparen, - rparen, - lbracket, - rbracket, - kw_typedef, - kw_const, - kw_vkapi_ptr, - kw_struct, - }; -}; - -const CTokenizer = struct { - source: []const u8, - offset: usize = 0, - - fn peek(self: CTokenizer) ?u8 { - return if (self.offset < self.source.len) self.source[self.offset] else null; - } - - fn consumeNoEof(self: *CTokenizer) u8 { - const c = self.peek().?; - self.offset += 1; - return c; - } - - fn consume(self: *CTokenizer) !u8 { - return if (self.offset < self.source.len) - return self.consumeNoEof() - else - return null; - } - - fn keyword(self: *CTokenizer) Token { - const start = self.offset; - _ = self.consumeNoEof(); - - while (true) { - const c = self.peek() orelse break; - switch (c) { - 'A'...'Z', 'a'...'z', '_', '0'...'9' => _ = self.consumeNoEof(), - else => break, - } - } - - const token_text = self.source[start .. self.offset]; - - const id = if (mem.eql(u8, token_text, "typedef")) - Token.Id.kw_typedef - else if (mem.eql(u8, token_text, "const")) - Token.Id.kw_const - else if (mem.eql(u8, token_text, "VKAPI_PTR")) - Token.Id.kw_vkapi_ptr - else if (mem.eql(u8, token_text, "struct")) - Token.Id.kw_struct - else - Token.Id.id; - - return .{.id = id, .text = token_text}; - } - - fn int(self: *CTokenizer) Token { - const start = self.offset; - _ = self.consumeNoEof(); - - // TODO: 123ABC is now legal - while (true) { - const c = self.peek() orelse break; - switch (c) { - '0'...'9' => _ = self.consumeNoEof(), - else => break, - } - } - - return .{ - .id = .int, - .text = self.source[start .. self.offset], - }; - } - - fn next(self: *CTokenizer) !?Token { - while (true) { - switch (self.peek() orelse return null) { - ' ', '\t', '\n', '\r' => _ = self.consumeNoEof(), - else => break, - } - } - - const c = self.peek().?; - var id: Token.Id = undefined; - switch (c) { - 'A'...'Z', 'a'...'z', '_' => return self.keyword(), - '0'...'9' => return self.int(), - '*' => id = .star, - ',' => id = .comma, - ';' => id = .semicolon, - ':' => id = .colon, - '[' => id = .lbracket, - ']' => id = .rbracket, - '(' => id = .lparen, - ')' => id = .rparen, - else => return error.UnexpectedCharacter - } - - const start = self.offset; - _ = self.consumeNoEof(); - return Token{ - .id = id, - .text = self.source[start .. self.offset] - }; - } -}; - -pub const XmlCTokenizer = struct { - it: xml.Element.ContentList.Iterator, - ctok: ?CTokenizer = null, - current: ?Token = null, - - pub fn init(elem: *xml.Element) XmlCTokenizer { - return .{ - .it = elem.children.iterator(0), - }; - } - - fn elemToToken(elem: *xml.Element) !?Token { - if (elem.children.count() != 1 or elem.children.at(0).* != .CharData) { - return error.InvalidXml; - } - - const text = elem.children.at(0).CharData; - if (mem.eql(u8, elem.tag, "type")) { - return Token{.id = .type_name, .text = text}; - } else if (mem.eql(u8, elem.tag, "enum")) { - return Token{.id = .enum_name, .text = text}; - } else if (mem.eql(u8, elem.tag, "name")) { - return Token{.id = .name, .text = text}; - } else if (mem.eql(u8, elem.tag, "comment")) { - return null; - } else { - return error.InvalidTag; - } - } - - fn next(self: *XmlCTokenizer) !?Token { - if (self.current) |current| { - const token = current; - self.current = null; - return token; - } - - while (true) { - if (self.ctok) |*ctok| { - if (try ctok.next()) |tok| { - return tok; - } - } - - self.ctok = null; - - if (self.it.next()) |child| { - switch (child.*) { - .CharData => |cdata| self.ctok = CTokenizer{.source = cdata}, - .Comment => {}, - .Element => |elem| if (try elemToToken(elem)) |tok| return tok, - } - } else { - return null; - } - } - } - - fn nextNoEof(self: *XmlCTokenizer) !Token { - return (try self.next()) orelse return error.InvalidSyntax; - } - - fn peek(self: *XmlCTokenizer) !?Token { - if (self.current) |current| { - return current; - } - - self.current = try self.next(); - return self.current; - } - - fn peekNoEof(self: *XmlCTokenizer) !Token { - return (try self.peek()) orelse return error.InvalidSyntax; - } - - fn expect(self: *XmlCTokenizer, id: Token.Id) !Token { - const tok = (try self.next()) orelse return error.UnexpectedEof; - if (tok.id != id) { - return error.UnexpectedToken; - } - - return tok; - } -}; - -// TYPEDEF = kw_typedef DECLARATION ';' -pub fn parseTypedef(allocator: *Allocator, xctok: *XmlCTokenizer) !registry.Declaration { - _ = try xctok.expect(.kw_typedef); - const decl = try parseDeclaration(allocator, xctok); - _ = try xctok.expect(.semicolon); - if (try xctok.peek()) |_| { - return error.InvalidSyntax; - } - - return registry.Declaration{ - .name = decl.name orelse return error.MissingTypeIdentifier, - .decl_type = decl.decl_type, - }; -} - -// MEMBER = DECLARATION (':' int)? -pub fn parseMember(allocator: *Allocator, xctok: *XmlCTokenizer) !registry.Container.Field { - const decl = try parseDeclaration(allocator, xctok); - var field = registry.Container.Field { - .name = decl.name orelse return error.MissingTypeIdentifier, - .field_type = decl.decl_type, - .bits = null, - }; - - if (try xctok.peek()) |tok| { - if (tok.id != .colon) { - return error.InvalidSyntax; - } - - _ = try xctok.nextNoEof(); - const bits = try xctok.expect(.int); - field.bits = try std.fmt.parseInt(usize, bits.text, 10); - - // Assume for now that there won't be any invalid C types like `char char* x : 4`. - - if (try xctok.peek()) |_| { - return error.InvalidSyntax; - } - } - - return field; -} - -pub fn parseParamOrProto(allocator: *Allocator, xctok: *XmlCTokenizer) !registry.Declaration { - const decl = try parseDeclaration(allocator, xctok); - if (try xctok.peek()) |_| { - return error.InvalidSyntax; - } - return registry.Declaration{ - .name = decl.name orelse return error.MissingTypeIdentifier, - .decl_type = decl.decl_type, - }; -} - -pub const Declaration = struct { - name: ?[]const u8, // Parameter names may be optional, especially in case of func(void) - decl_type: TypeInfo, -}; - -pub const ParseError = error{ - OutOfMemory, - InvalidSyntax, - InvalidTag, - InvalidXml, - Overflow, - UnexpectedEof, - UnexpectedCharacter, - UnexpectedToken, - MissingTypeIdentifier, -}; - -// DECLARATION = kw_const? type_name DECLARATOR -// DECLARATOR = POINTERS (id | name)? ('[' ARRAY_DECLARATOR ']')* -// | POINTERS '(' FNPTRSUFFIX -fn parseDeclaration(allocator: *Allocator, xctok: *XmlCTokenizer) ParseError!Declaration { - // Parse declaration constness - var tok = try xctok.nextNoEof(); - const inner_is_const = tok.id == .kw_const; - if (inner_is_const) { - tok = try xctok.nextNoEof(); - } - - if (tok.id == .kw_struct) { - tok = try xctok.nextNoEof(); - } - // Parse type name - if (tok.id != .type_name and tok.id != .id) return error.InvalidSyntax; - const type_name = tok.text; - - var type_info = TypeInfo{.alias = type_name}; - - // Parse pointers - type_info = try parsePointers(allocator, xctok, inner_is_const, type_info); - - // Parse name / fn ptr - - if (try parseFnPtrSuffix(allocator, xctok, type_info)) |decl| { - return decl; - } - - const name = blk: { - const name_tok = (try xctok.peek()) orelse break :blk null; - if (name_tok.id == .id or name_tok.id == .name) { - _ = try xctok.nextNoEof(); - break :blk name_tok.text; - } else { - break :blk null; - } - }; - - var inner_type = &type_info; - while (try parseArrayDeclarator(xctok)) |array_size| { - // Move the current inner type to a new node on the heap - const child = try allocator.create(TypeInfo); - child.* = inner_type.*; - - // Re-assign the previous inner type for the array type info node - inner_type.* = .{ - .array = .{ - .size = array_size, - .child = child, - } - }; - - // update the inner_type pointer so it points to the proper - // inner type again - inner_type = child; - } - - return Declaration{ - .name = name, - .decl_type = type_info, - }; -} - -// FNPTRSUFFIX = kw_vkapi_ptr '*' name' ')' '(' ('void' | (DECLARATION (',' DECLARATION)*)?) ')' -fn parseFnPtrSuffix(allocator: *Allocator, xctok: *XmlCTokenizer, return_type: TypeInfo) !?Declaration { - const lparen = try xctok.peek(); - if (lparen == null or lparen.?.id != .lparen) { - return null; - } - _ = try xctok.nextNoEof(); - _ = try xctok.expect(.kw_vkapi_ptr); - _ = try xctok.expect(.star); - const name = try xctok.expect(.name); - _ = try xctok.expect(.rparen); - _ = try xctok.expect(.lparen); - - const command = try allocator.create(registry.TypeInfo); - command.* = .{ - .command = .{ - .params = &[_]registry.Command.Param{}, - .return_type = try allocator.create(TypeInfo), - .success_codes = &[_][]const u8{}, - .error_codes = &[_][]const u8{}, - } - }; - - command.command.return_type.* = return_type; - const command_ptr = Declaration{ - .name = name.text, - .decl_type = .{ - .pointer = .{ - .is_const = true, - .size = .one, - .child = command, - } - }, - }; - - const first_param = try parseDeclaration(allocator, xctok); - if (first_param.name == null) { - if (first_param.decl_type != .alias or !mem.eql(u8, first_param.decl_type.alias, "void")) { - return error.InvalidSyntax; - } - - _ = try xctok.expect(.rparen); - return command_ptr; - } - - // There is no good way to estimate the number of parameters beforehand. - // Fortunately, there are usually a relatively low number of parameters to a function pointer, - // so an ArrayList backed by an arena allocator is good enough. - var params = std.ArrayList(registry.Command.Param).init(allocator); - try params.append(.{ - .name = first_param.name.?, - .param_type = first_param.decl_type, - }); - - while (true) { - switch ((try xctok.peekNoEof()).id) { - .rparen => break, - .comma => _ = try xctok.nextNoEof(), - else => return error.InvalidSyntax, - } - - const decl = try parseDeclaration(allocator, xctok); - try params.append(.{ - .name = decl.name orelse return error.MissingTypeIdentifier, - .param_type = decl.decl_type, - }); - } - - _ = try xctok.nextNoEof(); - command.command.params = params.toOwnedSlice(); - return command_ptr; -} - -// POINTERS = (kw_const? '*')* -fn parsePointers(allocator: *Allocator, xctok: *XmlCTokenizer, inner_const: bool, inner: TypeInfo) !TypeInfo { - var type_info = inner; - var first_const = inner_const; - - while (true) { - var tok = (try xctok.peek()) orelse return type_info; - var is_const = first_const; - first_const = false; - - if (tok.id == .kw_const) { - is_const = true; - _ = try xctok.nextNoEof(); - tok = (try xctok.peek()) orelse return type_info; - } - - if (tok.id != .star) { - // if `is_const` is true at this point, there was a trailing const, - // and the declaration itself is const. - return type_info; - } - - _ = try xctok.nextNoEof(); - - const child = try allocator.create(TypeInfo); - child.* = type_info; - - type_info = .{ - .pointer = .{ - .size = .one, // set elsewhere - .is_const = is_const or first_const, - .child = child, - }, - }; - } -} - -// ARRAY_DECLARATOR = '[' (int | enum_name) ']' -fn parseArrayDeclarator(xctok: *XmlCTokenizer) !?ArraySize { - const lbracket = try xctok.peek(); - if (lbracket == null or lbracket.?.id != .lbracket) { - return null; - } - - _ = try xctok.nextNoEof(); - - const size_tok = try xctok.nextNoEof(); - const size: ArraySize = switch (size_tok.id) { - .int => .{ - .int = std.fmt.parseInt(usize, size_tok.text, 10) catch |err| switch (err) { - error.Overflow => return error.Overflow, - error.InvalidCharacter => unreachable, - } - }, - .enum_name => .{.alias = size_tok.text}, - else => return error.InvalidSyntax - }; - - _ = try xctok.expect(.rbracket); - return size; -} - -fn testTokenizer(tokenizer: var, expected_tokens: []const Token) void { - for (expected_tokens) |expected| { - const tok = (tokenizer.next() catch unreachable).?; - testing.expectEqual(expected.id, tok.id); - testing.expectEqualSlices(u8, expected.text, tok.text); - } - - if (tokenizer.next() catch unreachable) |_| unreachable; -} - -test "CTokenizer" { - var ctok = CTokenizer { - .source = "typedef ([const)]** VKAPI_PTR 123,;aaaa" - }; - - testTokenizer( - &ctok, - &[_]Token{ - .{.id = .kw_typedef, .text = "typedef"}, - .{.id = .lparen, .text = "("}, - .{.id = .lbracket, .text = "["}, - .{.id = .kw_const, .text = "const"}, - .{.id = .rparen, .text = ")"}, - .{.id = .rbracket, .text = "]"}, - .{.id = .star, .text = "*"}, - .{.id = .star, .text = "*"}, - .{.id = .kw_vkapi_ptr, .text = "VKAPI_PTR"}, - .{.id = .int, .text = "123"}, - .{.id = .comma, .text = ","}, - .{.id = .semicolon, .text = ";"}, - .{.id = .id, .text = "aaaa"}, - } - ); -} - -test "XmlCTokenizer" { - const document = try xml.parse( - testing.allocator, - "typedef void (VKAPI_PTR *PFN_vkVoidFunction)(void);" - ); - defer document.deinit(); - - var xctok = XmlCTokenizer.init(document.root); - - testTokenizer( - &xctok, - &[_]Token{ - .{.id = .kw_typedef, .text = "typedef"}, - .{.id = .id, .text = "void"}, - .{.id = .lparen, .text = "("}, - .{.id = .kw_vkapi_ptr, .text = "VKAPI_PTR"}, - .{.id = .star, .text = "*"}, - .{.id = .name, .text = "PFN_vkVoidFunction"}, - .{.id = .rparen, .text = ")"}, - .{.id = .lparen, .text = "("}, - .{.id = .id, .text = "void"}, - .{.id = .rparen, .text = ")"}, - .{.id = .semicolon, .text = ";"}, - } - ); -} - -test "parseTypedef" { - const document = try xml.parse( - testing.allocator, - "typedef const struct Python* pythons[4];" - ); - defer document.deinit(); - - var arena = std.heap.ArenaAllocator.init(testing.allocator); - defer arena.deinit(); - - var xctok = XmlCTokenizer.init(document.root); - const decl = try parseTypedef(&arena.allocator, &xctok); - - testing.expectEqualSlices(u8, "pythons", decl.name); - testing.expectEqual(TypeInfo.array, decl.decl_type); - testing.expectEqual(ArraySize{.int = 4}, decl.decl_type.array.size); - const array_child = decl.decl_type.array.child.*; - testing.expectEqual(TypeInfo.pointer, array_child); - const ptr = array_child.pointer; - testing.expectEqual(true, ptr.is_const); - testing.expectEqual(TypeInfo.alias, ptr.child.*); - testing.expectEqualSlices(u8, "Python", ptr.child.alias); -} diff --git a/generator/spec-parse.zig b/generator/spec-parse.zig deleted file mode 100644 index 6e99ab3..0000000 --- a/generator/spec-parse.zig +++ /dev/null @@ -1,430 +0,0 @@ -const std = @import("std"); -const registry = @import("registry-new.zig"); -const xml = @import("xml.zig"); -const xmlc = @import("spec-c-parse.zig"); -const mem = std.mem; -const Allocator = mem.Allocator; -const ArenaAllocator = std.heap.ArenaAllocator; - -const api_constants_name = "API Constants"; - -pub const ParseResult = struct { - arena: ArenaAllocator, - registry: registry.Registry, - - pub fn deinit(self: ParseResult) void { - self.arena.deinit(); - } -}; - -pub fn parseXml(backing_allocator: *Allocator, root: *xml.Element) !ParseResult { - var arena = ArenaAllocator.init(backing_allocator); - errdefer arena.deinit(); - - const allocator = &arena.allocator; - - var reg = registry.Registry{ - .decls = try parseDeclarations(allocator, root), - .api_constants = try parseApiConstants(allocator, root), - .tags = try parseTags(allocator, root), - }; - - return ParseResult{ - .arena = arena, - .registry = reg, - }; -} - -fn parseDeclarations(allocator: *Allocator, root: *xml.Element) ![]registry.Declaration { - var types_elem = root.findChildByTag("types") orelse return error.InvalidRegistry; - var commands_elem = root.findChildByTag("commands") orelse return error.InvalidRegistry; - - const decl_upper_bound = types_elem.children.count() + commands_elem.children.count(); - const decls = try allocator.alloc(registry.Declaration, decl_upper_bound); - - var count: usize = 0; - count += try parseTypes(allocator, decls, types_elem); - count += try parseEnums(allocator, decls[count..], root); - count += try parseCommands(allocator, decls[count..], commands_elem); - return allocator.shrink(decls, count); -} - -fn parseTypes(allocator: *Allocator, out: []registry.Declaration, types_elem: *xml.Element) !usize { - var i: usize = 0; - var it = types_elem.findChildrenByTag("type"); - while (it.next()) |ty| { - out[i] = blk: { - const category = ty.getAttribute("category") orelse { - break :blk try parseForeigntype(ty); - }; - - // Enums are handled later, in parseEnums. This also has the effect of filtering - // out any enums which have no elements, and should be unused by other parts of the API. - if (mem.eql(u8, category, "bitmask")) { - break :blk try parseBitmaskType(ty); - } else if (mem.eql(u8, category, "handle")) { - break :blk try parseHandleType(ty); - } else if (mem.eql(u8, category, "basetype")) { - break :blk try parseBaseType(allocator, ty); - } else if (mem.eql(u8, category, "struct")) { - break :blk try parseContainer(allocator, ty, false); - } else if (mem.eql(u8, category, "union")) { - break :blk try parseContainer(allocator, ty, true); - } else if (mem.eql(u8, category, "funcpointer")) { - break :blk try parseFuncPointer(allocator, ty); - } - - continue; - }; - - i += 1; - } - - return i; -} - -fn parseForeigntype(ty: *xml.Element) !registry.Declaration { - const name = ty.getAttribute("name") orelse return error.InvalidRegistry; - const dependency = ty.getAttribute("requires") orelse if (mem.eql(u8, name, "int")) - "vk_platform" // for some reason, int doesn't depend on vk_platform (but the other c types do) - else - return error.InvalidRegistry; - - return registry.Declaration{ - .name = name, - .decl_type = .{.foreign = .{.dependency = dependency}}, - }; -} - -fn parseBitmaskType(ty: *xml.Element) !registry.Declaration { - if (ty.getAttribute("name")) |name| { - const alias = ty.getAttribute("alias") orelse return error.InvalidRegistry; - return registry.Declaration{ - .name = name, - .decl_type = .{.alias = alias}, - }; - } else { - return registry.Declaration{ - .name = ty.getCharData("name") orelse return error.InvalidRegistry, - .decl_type = .{.bitmask = .{.bits_enum = ty.getAttribute("requires")}}, - }; - } -} - -fn parseHandleType(ty: *xml.Element) !registry.Declaration { - // Parent is not handled in case of an alias - if (ty.getAttribute("name")) |name| { - const alias = ty.getAttribute("alias") orelse return error.InvalidRegistry; - return registry.Declaration{ - .name = name, - .decl_type = .{.alias = alias}, - }; - } else { - const name = ty.getCharData("name") orelse return error.InvalidRegistry; - const handle_type = ty.getCharData("type") orelse return error.InvalidRegistry; - const dispatchable = mem.eql(u8, handle_type, "VK_DEFINE_HANDLE"); - if (!dispatchable and !mem.eql(u8, handle_type, "VK_DEFINE_NON_DISPATCHABLE_HANDLE")) { - return error.InvalidRegistry; - } - - return registry.Declaration{ - .name = name, - .decl_type = .{ - .handle = .{ - .parent = ty.getAttribute("parent"), - .is_dispatchable = dispatchable, - } - }, - }; - } -} - -fn parseBaseType(allocator: *Allocator, ty: *xml.Element) !registry.Declaration { - const name = ty.getCharData("name") orelse return error.InvalidRegistry; - if (ty.getCharData("type")) |_| { // TODO: Parse as full type? - var tok = xmlc.XmlCTokenizer.init(ty); - return try xmlc.parseTypedef(allocator, &tok); - } else { - // Either ANativeWindow, AHardwareBuffer or CAMetalLayer. The latter has a lot of - // macros, which is why this part is not built into the xml/c parser. - return registry.Declaration{ - .name = name, - .decl_type = .{.opaque = {}}, - }; - } -} - -fn parseContainer(allocator: *Allocator, ty: *xml.Element, is_union: bool) !registry.Declaration { - const name = ty.getAttribute("name") orelse return error.InvalidRegistry; - - if (ty.getAttribute("alias")) |alias| { - return registry.Declaration{ - .name = name, - .decl_type = .{.alias = alias}, - }; - } - - var members = try allocator.alloc(registry.Container.Field, ty.children.count()); - - var i: usize = 0; - var it = ty.findChildrenByTag("member"); - while (it.next()) |member| { - var xctok = xmlc.XmlCTokenizer.init(member); - members[i] = try xmlc.parseMember(allocator, &xctok); - try parsePointerMeta(&members[i].field_type, member); - i += 1; - } - - return registry.Declaration{ - .name = name, - .decl_type = .{ - .container = .{ - .fields = allocator.shrink(members, i), - .is_union = is_union, - } - }, - }; -} - -fn parseFuncPointer(allocator: *Allocator, ty: *xml.Element) !registry.Declaration { - var xctok = xmlc.XmlCTokenizer.init(ty); - return try xmlc.parseTypedef(allocator, &xctok); -} - -fn lenToPointerSize(len: []const u8) registry.Pointer.PointerSize { - if (mem.eql(u8, len, "null-terminated")) { - return .zero_terminated; - } else { - return .many; - } -} - -fn parsePointerMeta(type_info: *registry.TypeInfo, elem: *xml.Element) !void { - if (elem.getAttribute("len")) |lens| { - var it = mem.split(lens, ","); - var current_type_info = type_info; - while (current_type_info.* == .pointer) { - const size = if (it.next()) |len_str| lenToPointerSize(len_str) else .one; - current_type_info.pointer.size = size; - current_type_info = current_type_info.pointer.child; - } - - if (it.next()) |_| { - // There are more elements in the `len` attribute than there are pointers - // Something probably went wrong - return error.InvalidRegistry; - } - } -} - -fn parseEnums(allocator: *Allocator, out: []registry.Declaration, root: *xml.Element) !usize { - var i: usize = 0; - var it = root.findChildrenByTag("enums"); - while (it.next()) |enums| { - const name = enums.getAttribute("name") orelse return error.InvalidRegistry; - if (mem.eql(u8, name, api_constants_name)) { - continue; - } - - out[i] = .{ - .name = name, - .decl_type = .{.enumeration = try parseEnumFields(allocator, enums)}, - }; - i += 1; - } - - return i; -} - -fn parseEnumFields(allocator: *Allocator, elem: *xml.Element) !registry.Enum { - // TODO: `type` was added recently, fall back to checking endswith FlagBits for older versions? - const enum_type = elem.getAttribute("type") orelse return error.InvalidRegistry; - const is_bitmask = mem.eql(u8, enum_type, "bitmask"); - if (!is_bitmask and !mem.eql(u8, enum_type, "enum")) { - return error.InvalidRegistry; - } - - const fields = try allocator.alloc(registry.Enum.Field, elem.children.count()); - - var i: usize = 0; - var it = elem.findChildrenByTag("enum"); - while (it.next()) |field| { - fields[i] = try parseEnumField(field); - i += 1; - } - - return registry.Enum{ - .fields = allocator.shrink(fields, i), - .is_bitmask = is_bitmask, - }; -} - -fn parseEnumField(field: *xml.Element) !registry.Enum.Field { - const is_compat_alias = if (field.getAttribute("comment")) |comment| - mem.eql(u8, comment, "Backwards-compatible alias containing a typo") or - mem.eql(u8, comment, "Deprecated name for backwards compatibility") - else - false; - - const name = field.getAttribute("name") orelse return error.InvalidRegistry; - const value: registry.Enum.Value = blk: { - // An enum variant's value could be defined by any of the following attributes: - // - value: Straight up value of the enum variant, in either base 10 or 16 (prefixed with 0x). - // - bitpos: Used for bitmasks, and can also be set in extensions. - // - alias: The field is an alias of another variant within the same enum. - // - offset: Used with features and extensions, where a non-bitpos value is added to an enum. - // The value is given by `1e9 + (extr_nr - 1) * 1e3 + offset`, where `ext_nr` is either - // given by the `extnumber` field (in the case of a feature), or given in the parent - // tag. In the latter case its passed via the `ext_nr` parameter. - // TODO: Handle `offset` elsewhere - if (field.getAttribute("value")) |value| { - if (mem.startsWith(u8, value, "0x")) { - break :blk .{.bit_vector = try std.fmt.parseInt(i32, value[2..], 16)}; - } else { - break :blk .{.int = try std.fmt.parseInt(i32, value, 10)}; - } - } else if (field.getAttribute("bitpos")) |bitpos| { - break :blk .{.bitpos = try std.fmt.parseInt(u5, bitpos, 10)}; - } else if (field.getAttribute("alias")) |alias| { - break :blk .{.alias = .{.alias_name = alias, .is_compat_alias = is_compat_alias}}; - } else { - return error.InvalidRegistry; - } - }; - - return registry.Enum.Field{ - .name = name, - .value = value, - }; -} - -fn parseCommands(allocator: *Allocator, out: []registry.Declaration, commands_elem: *xml.Element) !usize { - var i: usize = 0; - var it = commands_elem.findChildrenByTag("command"); - while (it.next()) |elem| { - out[i] = try parseCommand(allocator, elem); - i += 1; - } - - return i; -} - -fn splitResultCodes(allocator: *Allocator, text: []const u8) ![]const []const u8 { - var n_codes: usize = 1; - for (text) |c| { - if (c == ',') n_codes += 1; - } - - const codes = try allocator.alloc([]const u8, n_codes); - var it = mem.split(text, ","); - for (codes) |*code| { - code.* = it.next().?; - } - - return codes; -} - -fn parseCommand(allocator: *Allocator, elem: *xml.Element) !registry.Declaration { - if (elem.getAttribute("alias")) |alias| { - const name = elem.getAttribute("name") orelse return error.InvalidRegistry; - return registry.Declaration{ - .name = name, - .decl_type = .{.alias = alias} - }; - } - - const proto = elem.findChildByTag("proto") orelse return error.InvalidRegistry; - var proto_xctok = xmlc.XmlCTokenizer.init(proto); - const command_decl = try xmlc.parseParamOrProto(allocator, &proto_xctok); - - var params = try allocator.alloc(registry.Command.Param, elem.children.count()); - - var i: usize = 0; - var it = elem.findChildrenByTag("param"); - while (it.next()) |param| { - var xctok = xmlc.XmlCTokenizer.init(param); - const decl = try xmlc.parseParamOrProto(allocator, &xctok); - params[i] = .{.name = decl.name, .param_type = decl.decl_type}; - try parsePointerMeta(¶ms[i].param_type, param); - i += 1; - } - - const return_type = try allocator.create(registry.TypeInfo); - return_type.* = command_decl.decl_type; - - const success_codes = if (elem.getAttribute("successcodes")) |codes| - try splitResultCodes(allocator, codes) - else - &[_][]const u8{}; - - const error_codes = if (elem.getAttribute("errorcodes")) |codes| - try splitResultCodes(allocator, codes) - else - &[_][]const u8{}; - - return registry.Declaration{ - .name = command_decl.name, - .decl_type = .{ - .command = .{ - .params = allocator.shrink(params, i), - .return_type = return_type, - .success_codes = success_codes, - .error_codes = error_codes, - } - } - }; -} - -fn parseApiConstants(allocator: *Allocator, root: *xml.Element) ![]registry.ApiConstant { - var enums = blk: { - var it = root.findChildrenByTag("enums"); - while (it.next()) |child| { - const name = child.getAttribute("name") orelse continue; - if (mem.eql(u8, name, api_constants_name)) { - break :blk child; - } - } - - return error.InvalidRegistry; - }; - - const constants = try allocator.alloc(registry.ApiConstant, enums.children.count()); - - var i: usize = 0; - var it = enums.findChildrenByTag("enum"); - while (it.next()) |constant| { - const value = if (constant.getAttribute("value")) |expr| - registry.ApiConstant.Value{.expr = expr} - else if (constant.getAttribute("alias")) |alias| - registry.ApiConstant.Value{.alias = alias} - else - return error.InvalidRegistry; - - constants[i] = .{ - .name = constant.getAttribute("name") orelse return error.InvalidRegistry, - .value = value, - }; - - i += 1; - } - - return allocator.shrink(constants, i); -} - -fn parseTags(allocator: *Allocator, root: *xml.Element) ![]registry.Tag { - var tags_elem = root.findChildByTag("tags") orelse return error.InvalidRegistry; - const tags = try allocator.alloc(registry.Tag, tags_elem.children.count()); - - var i: usize = 0; - var it = tags_elem.findChildrenByTag("tag"); - while (it.next()) |tag| { - tags[i] = .{ - .name = tag.getAttribute("name") orelse return error.InvalidRegistry, - .author = tag.getAttribute("author") orelse return error.InvalidRegistry, - }; - - i += 1; - } - - return allocator.shrink(tags, i); -}