diff --git a/generator/registry/c-parse.zig b/generator/registry/c-parse.zig
new file mode 100644
index 0000000..fef44d7
--- /dev/null
+++ b/generator/registry/c-parse.zig
@@ -0,0 +1,575 @@
+const std = @import("std");
+const registry = @import("../registry.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/registry/parse.zig b/generator/registry/parse.zig
new file mode 100644
index 0000000..deb9c13
--- /dev/null
+++ b/generator/registry/parse.zig
@@ -0,0 +1,430 @@
+const std = @import("std");
+const registry = @import("../registry.zig");
+const xml = @import("../xml.zig");
+const xmlc = @import("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);
+}