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); +}